api_health.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. from fastapi import FastAPI, HTTPException
  2. from pydantic import BaseModel
  3. from datetime import datetime
  4. from typing import List, Dict, Optional
  5. import logging
  6. from database import DataFetcher
  7. from health_evalution_class import HealthAssessor
  8. import pandas as pd
  9. app = FastAPI()
  10. # 配置日志
  11. logging.basicConfig(level=logging.INFO)
  12. logger = logging.getLogger(__name__)
  13. #请求模型定义
  14. class AssessmentRequest(BaseModel):
  15. windcode: str
  16. month: str # 格式: "YYYY-MM"
  17. class SubsystemResult(BaseModel):
  18. health_score: float
  19. weights: Dict[str, float]
  20. # features: List[str]
  21. message: Optional[str] = None
  22. class AssessmentResult(BaseModel):
  23. engine_code: str
  24. wind_turbine_name:str
  25. mill_type: str
  26. total_health_score: Optional[float]
  27. subsystems: Dict[str, SubsystemResult]
  28. assessed_subsystems: List[str]
  29. @app.post("/health_assess", response_model=List[AssessmentResult])
  30. async def assess_windfarm(request: AssessmentRequest):
  31. try:
  32. datetime.strptime(request.month, "%Y-%m")
  33. except ValueError:
  34. raise HTTPException(status_code=400, detail="无效时间格式,使用YYYY-MM格式")
  35. logger.info(f"开始处理风场评估请求 - 风场编码: {request.windcode}, 月份: {request.month}")
  36. fetcher = DataFetcher()
  37. assessor = HealthAssessor()
  38. turbines = fetcher.get_turbines(request.windcode)
  39. print(turbines)
  40. if turbines.empty:
  41. raise HTTPException(status_code=404, detail="未找到风场数据")
  42. results: List[AssessmentResult] = [] # 显式声明列表类型并初始化
  43. all_turbines = len(turbines)
  44. processed_count = 0
  45. for idx, row in turbines.iterrows():
  46. try:
  47. engine_code = row['engine_code']
  48. mill_type_code = row['mill_type_code']
  49. wind_turbine_name = row['engine_name']
  50. # 获取机型类型
  51. mill_type_num = fetcher.get_mill_type(mill_type_code)
  52. mill_type = {1: 'dfig', 2: 'direct', 3: 'semi_direct'}.get(mill_type_num, 'unknown')
  53. if mill_type == 'unknown':
  54. logger.warning(f"{engine_code}机型未知,跳过处理")
  55. continue
  56. # 获取特征与数据
  57. # 先获取该风场所有可用列名
  58. available_columns = fetcher.get_turbine_columns(request.windcode)
  59. if not available_columns:
  60. continue
  61. # 获取有效特征列(基于实际列名)
  62. valid_features = assessor._get_all_possible_features(assessor, mill_type, available_columns)
  63. # 获取数据
  64. data = fetcher.fetch_turbine_data(request.windcode, engine_code, request.month, valid_features)
  65. print(data)
  66. if data is None or data.empty: # 增加对None的检查
  67. logger.warning(f"{engine_code}在{request.month}无有效数据")
  68. # 创建空评估结果,所有评分为-1
  69. empty_assessment = {
  70. 'engine_code': engine_code,
  71. 'wind_turbine_name': wind_turbine_name,
  72. 'mill_type': mill_type,
  73. 'total_health_score': -1,
  74. 'subsystems': {
  75. 'generator': {
  76. 'health_score': -1,
  77. 'weights': {},
  78. 'message': None
  79. },
  80. 'nacelle': {
  81. 'health_score': -1,
  82. 'weights': {},
  83. 'message': None
  84. },
  85. 'grid': {
  86. 'health_score': -1,
  87. 'weights': {},
  88. 'message': None
  89. },
  90. 'drive_train': {
  91. 'health_score': -1,
  92. 'weights': {},
  93. 'message': None
  94. }
  95. },
  96. 'assessed_subsystems': ['generator', 'nacelle', 'grid', 'drive_train']
  97. }
  98. formatted_result = _format_result(empty_assessment)
  99. results.append(formatted_result)
  100. processed_count += 1
  101. logger.info(f"处理无数据风机{engine_code}(已处理{processed_count}/{all_turbines}台)")
  102. continue
  103. # 执行评估并格式化结果
  104. assessment = assessor.assess_turbine(engine_code, data, mill_type, wind_turbine_name)
  105. if not assessment: # 检查评估结果有效性
  106. logger.warning(f"{engine_code}评估结果为空")
  107. continue
  108. formatted_result = _format_result(assessment)
  109. if isinstance(formatted_result, AssessmentResult): # 严格类型检查
  110. results.append(formatted_result)
  111. processed_count += 1
  112. logger.info(f"成功处理{engine_code}(已处理{processed_count}/{all_turbines}台)")
  113. else:
  114. logger.error(f"{engine_code}结果格式化失败,类型错误: {type(formatted_result)}")
  115. except Exception as e:
  116. logger.error(f"处理{engine_code}时发生异常: {str(e)}", exc_info=True)
  117. continue
  118. # 最终返回处理 - 强制转换为列表并确保类型正确
  119. if not results:
  120. logger.warning(f"风场{request.windcode}在{request.month}无有效评估结果,返回空列表")
  121. return []
  122. # 防御性类型检查(生产环境可移除)
  123. if not isinstance(results, list):
  124. logger.error(f"结果类型错误,期望list但得到{type(results)},强制转换为列表")
  125. results = [results] if results is not None else []
  126. return results
  127. def _format_result(assessment):
  128. """格式化评估结果"""
  129. return AssessmentResult(
  130. engine_code=assessment['engine_code'],
  131. wind_turbine_name=assessment['wind_turbine_name'],
  132. mill_type=assessment['mill_type'],
  133. total_health_score=assessment.get('total_health_score'),
  134. subsystems={
  135. k: SubsystemResult(**v)
  136. for k, v in assessment['subsystems'].items()
  137. },
  138. assessed_subsystems=assessment.get('assessed_subsystems', [])
  139. )