Przeglądaj źródła

重构Cd预测API,更新接口以支持区域名称和行政级别参数,添加从数据库和CSV文件生成作物Cd和有效态Cd预测地图的功能,删除不再使用的县市查询接口。

drggboy 6 dni temu
rodzic
commit
3449072d77
2 zmienionych plików z 255 dodań i 642 usunięć
  1. 251 642
      app/api/cd_prediction.py
  2. 4 0
      main.py

+ 251 - 642
app/api/cd_prediction.py

@@ -11,44 +11,14 @@ import logging
 import io
 import pandas as pd
 from ..services.cd_prediction_service_v3 import CdPredictionServiceV3
+from ..services.cd_prediction_database_service import CdPredictionDatabaseService
 
 router = APIRouter()
 
 # 设置日志
 logger = logging.getLogger(__name__)
 
-# =============================================================================
-# 县市查询接口
-# =============================================================================
 
-@router.get("/supported-counties", 
-           summary="获取支持的县市列表", 
-           description="获取系统当前支持进行Cd预测的县市列表")
-async def get_supported_counties() -> Dict[str, Any]:
-    """
-    获取支持的县市列表
-    
-    @returns {Dict[str, Any]} 支持的县市信息
-    """
-    try:
-        service = CdPredictionServiceV3()
-        counties = service.get_supported_counties_info()
-        
-        return {
-            "success": True,
-            "message": "获取支持县市列表成功",
-            "data": {
-                "counties": counties,
-                "total_count": len(counties)
-            }
-        }
-        
-    except Exception as e:
-        logger.error(f"获取支持县市列表失败: {str(e)}")
-        raise HTTPException(
-            status_code=500, 
-            detail=f"获取支持县市列表失败: {str(e)}"
-        )
 
 # =============================================================================
 # 一键生成并获取地图接口
@@ -56,10 +26,12 @@ async def get_supported_counties() -> Dict[str, Any]:
 
 @router.post("/crop-cd/generate-and-get-map", 
             summary="一键生成并获取作物Cd预测地图", 
-            description="根据县名和CSV数据生成作物Cd预测地图并直接返回图片文件")
+            description="根据区域名称和行政级别生成作物Cd预测地图并直接返回图片文件,优先使用数据库数据,也支持CSV文件上传")
 async def generate_and_get_crop_cd_map(
-    county_name: str = Form(..., description="县市名称,如:乐昌市"),
-    data_file: UploadFile = File(..., description="CSV格式的环境因子数据文件,前两列为经纬度,后续列与areatest.csv结构一致"),
+    area: str = Form(..., description="区域名称,如:乐昌市"),
+    level: str = Form("auto", description="行政级别,如:county, city, province,默认为auto自动识别"),
+    use_database: Optional[bool] = Form(True, description="是否使用数据库数据,默认为True"),
+    data_file: Optional[UploadFile] = File(None, description="可选的CSV格式环境因子数据文件,仅在use_database=False时使用"),
     enable_interpolation: Optional[bool] = Form(None, description="是否启用空间插值"),
     interpolation_method: Optional[str] = Form(None, description="插值方法: nearest, linear, cubic"), 
     resolution_factor: Optional[float] = Form(None, description="分辨率因子,越大分辨率越高")
@@ -67,43 +39,14 @@ async def generate_and_get_crop_cd_map(
     """
     一键生成并获取作物Cd预测地图
     
-    @param county_name: 县市名称
-    @param data_file: CSV数据文件,前两列为经纬度坐标,后面几列和areatest.csv的结构一致
+    @param area: 区域名称
+    @param level: 行政级别
+    @param use_database: 是否使用数据库数据
+    @param data_file: 可选的CSV数据文件,仅在use_database=False时使用
     @returns {FileResponse} 预测地图文件
     """
     try:
-        logger.info(f"开始为{county_name}一键生成作物Cd预测地图")
-        
-        # 验证文件格式
-        if not data_file.filename.endswith('.csv'):
-            raise HTTPException(status_code=400, detail="仅支持CSV格式文件")
-        
-        # 读取CSV数据
-        content = await data_file.read()
-        df = pd.read_csv(io.StringIO(content.decode('utf-8')))
-        
-        # 验证数据格式
-        if df.shape[1] < 3:
-            raise HTTPException(
-                status_code=400, 
-                detail="数据至少需要3列:前两列为经纬度,后续列为环境因子"
-            )
-        
-        # 重命名前两列为标准的经纬度列名
-        df.columns = ['longitude', 'latitude'] + list(df.columns[2:])
-        
-        service = CdPredictionServiceV3()
-        
-        # 验证数据
-        validation_result = service.validate_input_data(df, county_name)
-        if not validation_result['valid']:
-            raise HTTPException(
-                status_code=400,
-                detail=f"数据验证失败: {', '.join(validation_result['errors'])}"
-            )
-        
-        # 保存临时数据文件
-        temp_file_path = service.save_temp_data(df, county_name)
+        logger.info(f"开始为{area}({level})一键生成作物Cd预测地图(数据源:{'数据库' if use_database else 'CSV文件'})")
         
         # 构建栅格配置参数
         raster_params = {}
@@ -114,81 +57,103 @@ async def generate_and_get_crop_cd_map(
         if resolution_factor is not None:
             raster_params['resolution_factor'] = resolution_factor
         
-        # 生成预测结果
-        result = await service.generate_crop_cd_prediction_for_county(
-            county_name=county_name,
-            data_file=temp_file_path,
-            raster_config_override=raster_params if raster_params else None
-        )
-        
-        if not result['map_path'] or not os.path.exists(result['map_path']):
-            raise HTTPException(status_code=500, detail="地图文件生成失败")
+        if use_database:
+            # 使用数据库数据生成预测
+            db_service = CdPredictionDatabaseService()
+            
+            result = await db_service.generate_crop_cd_prediction_from_database(
+                area=area,
+                level=level,
+                raster_config_override=raster_params if raster_params else None
+            )
+            
+            if not result['map_path'] or not os.path.exists(result['map_path']):
+                raise HTTPException(status_code=500, detail="基于数据库数据的地图文件生成失败")
+            
+            logger.info(f"使用数据库数据为{area}({level})生成作物Cd预测地图成功,处理{result.get('processed_records', 0)}条记录")
+            
+        else:
+            # 使用上传的CSV文件生成预测
+            if not data_file:
+                raise HTTPException(status_code=400, detail="当use_database=False时,必须提供data_file")
+            
+            # 验证文件格式
+            if not data_file.filename.endswith('.csv'):
+                raise HTTPException(status_code=400, detail="仅支持CSV格式文件")
+            
+            # 读取CSV数据
+            content = await data_file.read()
+            df = pd.read_csv(io.StringIO(content.decode('utf-8')))
+            
+            # 验证数据格式
+            if df.shape[1] < 3:
+                raise HTTPException(
+                    status_code=400, 
+                    detail="数据至少需要3列:前两列为经纬度,后续列为环境因子"
+                )
+            
+            # 重命名前两列为标准的经纬度列名
+            df.columns = ['longitude', 'latitude'] + list(df.columns[2:])
+            
+            service = CdPredictionServiceV3()
+            
+            # 验证数据
+            validation_result = service.validate_input_data(df, area)
+            if not validation_result['valid']:
+                raise HTTPException(
+                    status_code=400,
+                    detail=f"数据验证失败: {', '.join(validation_result['errors'])}"
+                )
+            
+            # 保存临时数据文件
+            temp_file_path = service.save_temp_data(df, area)
+            
+            # 生成预测结果
+            result = await service.generate_crop_cd_prediction_for_county(
+                county_name=area,
+                data_file=temp_file_path,
+                raster_config_override=raster_params if raster_params else None
+            )
+            
+            if not result['map_path'] or not os.path.exists(result['map_path']):
+                raise HTTPException(status_code=500, detail="基于CSV文件的地图文件生成失败")
+            
+            logger.info(f"使用CSV文件为{area}({level})生成作物Cd预测地图成功")
         
         return FileResponse(
             path=result['map_path'],
-            filename=f"{county_name}_crop_cd_prediction_map.jpg",
+            filename=f"{area}_crop_cd_prediction_map.jpg",
             media_type="image/jpeg"
         )
         
     except HTTPException:
         raise
     except Exception as e:
-        logger.error(f"为{county_name}一键生成作物Cd预测地图失败: {str(e)}")
+        logger.error(f"为{area}({level})一键生成作物Cd预测地图失败: {str(e)}")
         raise HTTPException(
             status_code=500, 
-            detail=f"为{county_name}一键生成作物Cd预测地图失败: {str(e)}"
+            detail=f"为{area}({level})一键生成作物Cd预测地图失败: {str(e)}"
         )
 
-@router.post("/effective-cd/generate-and-get-map", 
-            summary="一键生成并获取有效态Cd预测地图", 
-            description="根据县名和CSV数据生成有效态Cd预测地图并直接返回图片文件")
-async def generate_and_get_effective_cd_map(
-    county_name: str = Form(..., description="县市名称,如:乐昌市"),
-    data_file: UploadFile = File(..., description="CSV格式的环境因子数据文件,前两列为经纬度,后续列与areatest.csv结构一致"),
+@router.post("/crop-cd/generate-from-database", 
+            summary="基于数据库数据生成作物Cd预测地图", 
+            description="直接从数据库读取数据生成作物Cd预测地图")
+async def generate_crop_cd_map_from_database(
+    area: str = Form(..., description="区域名称,如:乐昌市"),
+    level: str = Form("auto", description="行政级别,如:county, city, province,默认为auto自动识别"),
     enable_interpolation: Optional[bool] = Form(None, description="是否启用空间插值"),
     interpolation_method: Optional[str] = Form(None, description="插值方法: nearest, linear, cubic"), 
     resolution_factor: Optional[float] = Form(None, description="分辨率因子,越大分辨率越高")
 ):
     """
-    一键生成并获取有效态Cd预测地图
+    基于数据库数据生成作物Cd预测地图
     
-    @param county_name: 县市名称
-    @param data_file: CSV数据文件,前两列为经纬度坐标,后面几列和areatest.csv的结构一致
+    @param area: 区域名称
+    @param level: 行政级别
     @returns {FileResponse} 预测地图文件
     """
     try:
-        logger.info(f"开始为{county_name}一键生成有效态Cd预测地图")
-        
-        # 验证文件格式
-        if not data_file.filename.endswith('.csv'):
-            raise HTTPException(status_code=400, detail="仅支持CSV格式文件")
-        
-        # 读取CSV数据
-        content = await data_file.read()
-        df = pd.read_csv(io.StringIO(content.decode('utf-8')))
-        
-        # 验证数据格式
-        if df.shape[1] < 3:
-            raise HTTPException(
-                status_code=400, 
-                detail="数据至少需要3列:前两列为经纬度,后续列为环境因子"
-            )
-        
-        # 重命名前两列为标准的经纬度列名
-        df.columns = ['longitude', 'latitude'] + list(df.columns[2:])
-        
-        service = CdPredictionServiceV3()
-        
-        # 验证数据
-        validation_result = service.validate_input_data(df, county_name)
-        if not validation_result['valid']:
-            raise HTTPException(
-                status_code=400,
-                detail=f"数据验证失败: {', '.join(validation_result['errors'])}"
-            )
-        
-        # 保存临时数据文件
-        temp_file_path = service.save_temp_data(df, county_name)
+        logger.info(f"开始基于数据库数据为{area}({level})生成作物Cd预测地图")
         
         # 构建栅格配置参数
         raster_params = {}
@@ -199,63 +164,58 @@ async def generate_and_get_effective_cd_map(
         if resolution_factor is not None:
             raster_params['resolution_factor'] = resolution_factor
         
-        # 生成预测结果
-        result = await service.generate_effective_cd_prediction_for_county(
-            county_name=county_name,
-            data_file=temp_file_path,
+        # 使用数据库数据生成预测
+        db_service = CdPredictionDatabaseService()
+        
+        result = await db_service.generate_crop_cd_prediction_from_database(
+            area=area,
+            level=level,
             raster_config_override=raster_params if raster_params else None
         )
         
         if not result['map_path'] or not os.path.exists(result['map_path']):
-            raise HTTPException(status_code=500, detail="地图文件生成失败")
+            raise HTTPException(status_code=500, detail="基于数据库数据的地图文件生成失败")
+        
+        logger.info(f"基于数据库数据为{area}({level})生成作物Cd预测地图成功,处理{result.get('processed_records', 0)}条记录")
         
         return FileResponse(
             path=result['map_path'],
-            filename=f"{county_name}_effective_cd_prediction_map.jpg",
+            filename=f"{area}_crop_cd_prediction_map_database.jpg",
             media_type="image/jpeg"
         )
         
     except HTTPException:
         raise
     except Exception as e:
-        logger.error(f"为{county_name}一键生成有效态Cd预测地图失败: {str(e)}")
+        logger.error(f"基于数据库数据为{area}({level})生成作物Cd预测地图失败: {str(e)}")
         raise HTTPException(
             status_code=500, 
-            detail=f"为{county_name}一键生成有效态Cd预测地图失败: {str(e)}"
+            detail=f"基于数据库数据为{area}({level})生成作物Cd预测地图失败: {str(e)}"
         )
 
-# =============================================================================
-# 基于数据库的有效态镉预测接口
-# =============================================================================
-
-@router.post("/effective-cd/generate-from-database", 
-            summary="基于数据库数据生成有效态Cd预测并更新结果表", 
-            description="从EffCd_input_data和Farmland_data表中读取数据,进行有效态Cd预测,并将结果保存到EffCd_output_data表")
-async def generate_effective_cd_from_database(
-    area: str = Form(..., description="地区名称,如:乐昌市"),
-    level: str = Form(..., description="行政级别,如:县级市、县、市辖区等"),
+@router.post("/effective-cd/generate-and-get-map", 
+            summary="一键生成并获取有效态Cd预测地图", 
+            description="根据区域名称和行政级别生成有效态Cd预测地图并直接返回图片文件,优先使用数据库数据,也支持CSV文件上传")
+async def generate_and_get_effective_cd_map(
+    area: str = Form(..., description="区域名称,如:乐昌市"),
+    level: str = Form("auto", description="行政级别,如:county, city, province,默认为auto自动识别"),
+    use_database: Optional[bool] = Form(True, description="是否使用数据库数据,默认为True"),
+    data_file: Optional[UploadFile] = File(None, description="可选的CSV格式环境因子数据文件,仅在use_database=False时使用"),
     enable_interpolation: Optional[bool] = Form(None, description="是否启用空间插值"),
     interpolation_method: Optional[str] = Form(None, description="插值方法: nearest, linear, cubic"), 
     resolution_factor: Optional[float] = Form(None, description="分辨率因子,越大分辨率越高")
 ):
     """
-    基于数据库数据生成有效态Cd预测并更新结果表
+    一键生成并获取有效态Cd预测地图
     
-    @param area: 区名称
+    @param area: 区名称
     @param level: 行政级别
-    @param enable_interpolation: 是否启用空间插值
-    @param interpolation_method: 插值方法
-    @param resolution_factor: 分辨率因子
-    @returns {dict} 预测结果信息
+    @param use_database: 是否使用数据库数据
+    @param data_file: 可选的CSV数据文件,仅在use_database=False时使用
+    @returns {FileResponse} 预测地图文件
     """
     try:
-        from ..services.cd_prediction_service_v3 import CdPredictionServiceV3
-        from ..services.cd_prediction_database_service import CdPredictionDatabaseService
-        
-        logger.info(f"开始基于数据库数据生成有效态Cd预测: {area} ({level})")
-        
-        # 初始化服务
-        db_service = CdPredictionDatabaseService()
+        logger.info(f"开始为{area}({level})一键生成有效态Cd预测地图(数据源:{'数据库' if use_database else 'CSV文件'})")
         
         # 构建栅格配置参数
         raster_params = {}
@@ -266,69 +226,103 @@ async def generate_effective_cd_from_database(
         if resolution_factor is not None:
             raster_params['resolution_factor'] = resolution_factor
         
-        # 执行基于数据库的有效态Cd预测
-        result = await db_service.generate_effective_cd_prediction_from_database(
-            area=area,
-            level=level,
-            raster_config_override=raster_params if raster_params else None
-        )
+        if use_database:
+            # 使用数据库数据生成预测
+            db_service = CdPredictionDatabaseService()
+            
+            result = await db_service.generate_effective_cd_prediction_from_database(
+                area=area,
+                level=level,
+                raster_config_override=raster_params if raster_params else None
+            )
+            
+            if not result['map_path'] or not os.path.exists(result['map_path']):
+                raise HTTPException(status_code=500, detail="基于数据库数据的地图文件生成失败")
+            
+            logger.info(f"使用数据库数据为{area}({level})生成有效态Cd预测地图成功,处理{result.get('processed_records', 0)}条记录")
+            
+        else:
+            # 使用上传的CSV文件生成预测
+            if not data_file:
+                raise HTTPException(status_code=400, detail="当use_database=False时,必须提供data_file")
+            
+            # 验证文件格式
+            if not data_file.filename.endswith('.csv'):
+                raise HTTPException(status_code=400, detail="仅支持CSV格式文件")
+            
+            # 读取CSV数据
+            content = await data_file.read()
+            df = pd.read_csv(io.StringIO(content.decode('utf-8')))
+            
+            # 验证数据格式
+            if df.shape[1] < 3:
+                raise HTTPException(
+                    status_code=400, 
+                    detail="数据至少需要3列:前两列为经纬度,后续列为环境因子"
+                )
+            
+            # 重命名前两列为标准的经纬度列名
+            df.columns = ['longitude', 'latitude'] + list(df.columns[2:])
+            
+            service = CdPredictionServiceV3()
+            
+            # 验证数据
+            validation_result = service.validate_input_data(df, area)
+            if not validation_result['valid']:
+                raise HTTPException(
+                    status_code=400,
+                    detail=f"数据验证失败: {', '.join(validation_result['errors'])}"
+                )
+            
+            # 保存临时数据文件
+            temp_file_path = service.save_temp_data(df, area)
+            
+            # 生成预测结果
+            result = await service.generate_effective_cd_prediction_for_county(
+                county_name=area,
+                data_file=temp_file_path,
+                raster_config_override=raster_params if raster_params else None
+            )
+            
+            if not result['map_path'] or not os.path.exists(result['map_path']):
+                raise HTTPException(status_code=500, detail="基于CSV文件的地图文件生成失败")
+            
+            logger.info(f"使用CSV文件为{area}({level})生成有效态Cd预测地图成功")
         
-        logger.info(f"基于数据库数据的有效态Cd预测完成: {area} ({level})")
-        
-        return {
-            "success": True,
-            "message": f"成功为{area}生成有效态Cd预测",
-            "area": area,
-            "level": level,
-            "processed_records": result.get('processed_records', 0),
-            "updated_records": result.get('updated_records', 0),
-            "map_path": result.get('map_path'),
-            "histogram_path": result.get('histogram_path'),
-            "timestamp": result.get('timestamp'),
-            "validation": result.get('validation', {})
-        }
+        return FileResponse(
+            path=result['map_path'],
+            filename=f"{area}_effective_cd_prediction_map.jpg",
+            media_type="image/jpeg"
+        )
         
     except HTTPException:
         raise
     except Exception as e:
-        logger.error(f"基于数据库数据的有效态Cd预测失败: {str(e)}")
+        logger.error(f"为{area}({level})一键生成有效态Cd预测地图失败: {str(e)}")
         raise HTTPException(
             status_code=500, 
-            detail=f"基于数据库数据的有效态Cd预测失败: {str(e)}"
+            detail=f"为{area}({level})一键生成有效态Cd预测地图失败: {str(e)}"
         )
 
-# =============================================================================
-# 基于数据库的作物镉预测接口
-# =============================================================================
-
-@router.post("/crop-cd/generate-from-database", 
-            summary="基于数据库数据生成作物Cd预测并更新结果表", 
-            description="从CropCd_input_data和Farmland_data表中读取数据,进行作物Cd预测,并将结果保存到CropCd_output_data表")
-async def generate_crop_cd_from_database(
-    area: str = Form(..., description="地区名称,如:乐昌市"),
-    level: str = Form(..., description="行政级别,如:县级市、县、市辖区等"),
+@router.post("/effective-cd/generate-from-database", 
+            summary="基于数据库数据生成有效态Cd预测地图", 
+            description="直接从数据库读取数据生成有效态Cd预测地图")
+async def generate_effective_cd_map_from_database(
+    area: str = Form(..., description="区域名称,如:乐昌市"),
+    level: str = Form("auto", description="行政级别,如:county, city, province,默认为auto自动识别"),
     enable_interpolation: Optional[bool] = Form(None, description="是否启用空间插值"),
     interpolation_method: Optional[str] = Form(None, description="插值方法: nearest, linear, cubic"), 
     resolution_factor: Optional[float] = Form(None, description="分辨率因子,越大分辨率越高")
 ):
     """
-    基于数据库数据生成作物Cd预测并更新结果表
+    基于数据库数据生成有效态Cd预测地图
     
-    @param area: 地区名称
+    @param area: 区名称
     @param level: 行政级别
-    @param enable_interpolation: 是否启用空间插值
-    @param interpolation_method: 插值方法
-    @param resolution_factor: 分辨率因子
-    @returns {dict} 预测结果信息
+    @returns {FileResponse} 预测地图文件
     """
     try:
-        from ..services.cd_prediction_service_v3 import CdPredictionServiceV3
-        from ..services.cd_prediction_database_service import CdPredictionDatabaseService
-        
-        logger.info(f"开始基于数据库数据生成作物Cd预测: {area} ({level})")
-        
-        # 初始化服务
-        db_service = CdPredictionDatabaseService()
+        logger.info(f"开始基于数据库数据为{area}({level})生成有效态Cd预测地图")
         
         # 构建栅格配置参数
         raster_params = {}
@@ -339,65 +333,56 @@ async def generate_crop_cd_from_database(
         if resolution_factor is not None:
             raster_params['resolution_factor'] = resolution_factor
         
-        # 执行基于数据库的作物Cd预测
-        result = await db_service.generate_crop_cd_prediction_from_database(
+        # 使用数据库数据生成预测
+        db_service = CdPredictionDatabaseService()
+        
+        result = await db_service.generate_effective_cd_prediction_from_database(
             area=area,
             level=level,
             raster_config_override=raster_params if raster_params else None
         )
         
-        logger.info(f"基于数据库数据的作物Cd预测完成: {area} ({level})")
-        
-        return {
-            "success": True,
-            "message": f"成功为{area}生成作物Cd预测",
-            "area": area,
-            "level": level,
-            "processed_records": result.get('processed_records', 0),
-            "updated_records": result.get('updated_records', 0),
-            "map_path": result.get('map_path'),
-            "histogram_path": result.get('histogram_path'),
-            "timestamp": result.get('timestamp'),
-            "validation": result.get('validation', {})
-        }
+        if not result['map_path'] or not os.path.exists(result['map_path']):
+            raise HTTPException(status_code=500, detail="基于数据库数据的地图文件生成失败")
+        
+        logger.info(f"基于数据库数据为{area}({level})生成有效态Cd预测地图成功,处理{result.get('processed_records', 0)}条记录")
+        
+        return FileResponse(
+            path=result['map_path'],
+            filename=f"{area}_effective_cd_prediction_map_database.jpg",
+            media_type="image/jpeg"
+        )
         
     except HTTPException:
         raise
     except Exception as e:
-        logger.error(f"基于数据库数据的作物Cd预测失败: {str(e)}")
+        logger.error(f"基于数据库数据为{area}({level})生成有效态Cd预测地图失败: {str(e)}")
         raise HTTPException(
             status_code=500, 
-            detail=f"基于数据库数据的作物Cd预测失败: {str(e)}"
+            detail=f"基于数据库数据为{area}({level})生成有效态Cd预测地图失败: {str(e)}"
         )
 
 # =============================================================================
 # 获取最新预测结果接口(无需重新计算)
 # =============================================================================
 
-@router.get("/crop-cd/latest-map/{county_name}", 
+@router.get("/crop-cd/latest-map/{area_name}", 
            summary="获取作物Cd最新地图", 
-           description="直接返回指定县市的最新作物Cd预测地图,无需重新计算")
-async def get_latest_crop_cd_map(county_name: str):
+           description="直接返回指定区域的最新作物Cd预测地图,无需重新计算")
+async def get_latest_crop_cd_map(area_name: str):
     """
-    获取指定县市的最新作物Cd预测地图
+    获取指定区域的最新作物Cd预测地图
     
-    @param county_name: 县市名称,如:乐昌市
+    @param area_name: 区域名称,如:乐昌市
     @returns {FileResponse} 最新的预测地图文件
     """
     try:
-        logger.info(f"获取{county_name}的最新作物Cd预测地图")
+        logger.info(f"获取{area_name}的最新作物Cd预测地图")
         
         service = CdPredictionServiceV3()
         
-        # 验证县市是否支持
-        if not service.is_county_supported(county_name):
-            raise HTTPException(
-                status_code=404, 
-                detail=f"不支持的县市: {county_name}"
-            )
-        
         # 查找最新的地图文件
-        map_pattern = f"prediction_map_crop_cd_{county_name}_*.jpg"
+        map_pattern = f"prediction_map_crop_cd_{area_name}_*.jpg"
         map_files = []
         
         # 在输出目录中查找相关文件
@@ -409,7 +394,7 @@ async def get_latest_crop_cd_map(county_name: str):
         if not map_files:
             raise HTTPException(
                 status_code=404, 
-                detail=f"未找到{county_name}的作物Cd预测地图,请先执行预测"
+                detail=f"未找到{area_name}的作物Cd预测地图,请先执行预测"
             )
         
         # 选择最新的文件(按修改时间排序)
@@ -420,43 +405,36 @@ async def get_latest_crop_cd_map(county_name: str):
         
         return FileResponse(
             path=latest_map,
-            filename=f"{county_name}_latest_crop_cd_map.jpg",
+            filename=f"{area_name}_latest_crop_cd_map.jpg",
             media_type="image/jpeg"
         )
         
     except HTTPException:
         raise
     except Exception as e:
-        logger.error(f"获取{county_name}最新作物Cd地图失败: {str(e)}")
+        logger.error(f"获取{area_name}最新作物Cd地图失败: {str(e)}")
         raise HTTPException(
             status_code=500, 
-            detail=f"获取{county_name}最新作物Cd地图失败: {str(e)}"
+            detail=f"获取{area_name}最新作物Cd地图失败: {str(e)}"
         )
 
-@router.get("/effective-cd/latest-map/{county_name}", 
+@router.get("/effective-cd/latest-map/{area_name}", 
            summary="获取有效态Cd最新地图", 
-           description="直接返回指定县市的最新有效态Cd预测地图,无需重新计算")
-async def get_latest_effective_cd_map(county_name: str):
+           description="直接返回指定区域的最新有效态Cd预测地图,无需重新计算")
+async def get_latest_effective_cd_map(area_name: str):
     """
-    获取指定县市的最新有效态Cd预测地图
+    获取指定区域的最新有效态Cd预测地图
     
-    @param county_name: 县市名称,如:乐昌市
+    @param area_name: 区域名称,如:乐昌市
     @returns {FileResponse} 最新的预测地图文件
     """
     try:
-        logger.info(f"获取{county_name}的最新有效态Cd预测地图")
+        logger.info(f"获取{area_name}的最新有效态Cd预测地图")
         
         service = CdPredictionServiceV3()
         
-        # 验证县市是否支持
-        if not service.is_county_supported(county_name):
-            raise HTTPException(
-                status_code=404, 
-                detail=f"不支持的县市: {county_name}"
-            )
-        
         # 查找最新的地图文件
-        map_pattern = f"prediction_map_effective_cd_{county_name}_*.jpg"
+        map_pattern = f"prediction_map_effective_cd_{area_name}_*.jpg"
         map_files = []
         
         # 在输出目录中查找相关文件
@@ -468,7 +446,7 @@ async def get_latest_effective_cd_map(county_name: str):
         if not map_files:
             raise HTTPException(
                 status_code=404, 
-                detail=f"未找到{county_name}的有效态Cd预测地图,请先执行预测"
+                detail=f"未找到{area_name}的有效态Cd预测地图,请先执行预测"
             )
         
         # 选择最新的文件(按修改时间排序)
@@ -479,43 +457,36 @@ async def get_latest_effective_cd_map(county_name: str):
         
         return FileResponse(
             path=latest_map,
-            filename=f"{county_name}_latest_effective_cd_map.jpg",
+            filename=f"{area_name}_latest_effective_cd_map.jpg",
             media_type="image/jpeg"
         )
         
     except HTTPException:
         raise
     except Exception as e:
-        logger.error(f"获取{county_name}最新有效态Cd地图失败: {str(e)}")
+        logger.error(f"获取{area_name}最新有效态Cd地图失败: {str(e)}")
         raise HTTPException(
             status_code=500, 
-            detail=f"获取{county_name}最新有效态Cd地图失败: {str(e)}"
+            detail=f"获取{area_name}最新有效态Cd地图失败: {str(e)}"
         )
 
-@router.get("/crop-cd/latest-histogram/{county_name}", 
+@router.get("/crop-cd/latest-histogram/{area_name}", 
            summary="获取作物Cd最新直方图", 
-           description="直接返回指定县市的最新作物Cd预测直方图,无需重新计算")
-async def get_latest_crop_cd_histogram(county_name: str):
+           description="直接返回指定区域的最新作物Cd预测直方图,无需重新计算")
+async def get_latest_crop_cd_histogram(area_name: str):
     """
-    获取指定县市的最新作物Cd预测直方图
+    获取指定区域的最新作物Cd预测直方图
     
-    @param county_name: 县市名称,如:乐昌市
+    @param area_name: 区域名称,如:乐昌市
     @returns {FileResponse} 最新的预测直方图文件
     """
     try:
-        logger.info(f"获取{county_name}的最新作物Cd预测直方图")
+        logger.info(f"获取{area_name}的最新作物Cd预测直方图")
         
         service = CdPredictionServiceV3()
         
-        # 验证县市是否支持
-        if not service.is_county_supported(county_name):
-            raise HTTPException(
-                status_code=404, 
-                detail=f"不支持的县市: {county_name}"
-            )
-        
         # 查找最新的直方图文件
-        histogram_pattern = f"prediction_histogram_crop_cd_{county_name}_*.jpg"
+        histogram_pattern = f"prediction_histogram_crop_cd_{area_name}_*.jpg"
         histogram_files = []
         
         # 在输出目录中查找相关文件
@@ -527,7 +498,7 @@ async def get_latest_crop_cd_histogram(county_name: str):
         if not histogram_files:
             raise HTTPException(
                 status_code=404, 
-                detail=f"未找到{county_name}的作物Cd预测直方图,请先执行预测"
+                detail=f"未找到{area_name}的作物Cd预测直方图,请先执行预测"
             )
         
         # 选择最新的文件(按修改时间排序)
@@ -538,43 +509,36 @@ async def get_latest_crop_cd_histogram(county_name: str):
         
         return FileResponse(
             path=latest_histogram,
-            filename=f"{county_name}_latest_crop_cd_histogram.jpg",
+            filename=f"{area_name}_latest_crop_cd_histogram.jpg",
             media_type="image/jpeg"
         )
         
     except HTTPException:
         raise
     except Exception as e:
-        logger.error(f"获取{county_name}最新作物Cd直方图失败: {str(e)}")
+        logger.error(f"获取{area_name}最新作物Cd直方图失败: {str(e)}")
         raise HTTPException(
             status_code=500, 
-            detail=f"获取{county_name}最新作物Cd直方图失败: {str(e)}"
+            detail=f"获取{area_name}最新作物Cd直方图失败: {str(e)}"
         )
 
-@router.get("/effective-cd/latest-histogram/{county_name}", 
+@router.get("/effective-cd/latest-histogram/{area_name}", 
            summary="获取有效态Cd最新直方图", 
-           description="直接返回指定县市的最新有效态Cd预测直方图,无需重新计算")
-async def get_latest_effective_cd_histogram(county_name: str):
+           description="直接返回指定区域的最新有效态Cd预测直方图,无需重新计算")
+async def get_latest_effective_cd_histogram(area_name: str):
     """
-    获取指定县市的最新有效态Cd预测直方图
+    获取指定区域的最新有效态Cd预测直方图
     
-    @param county_name: 县市名称,如:乐昌市
+    @param area_name: 区域名称,如:乐昌市
     @returns {FileResponse} 最新的预测直方图文件
     """
     try:
-        logger.info(f"获取{county_name}的最新有效态Cd预测直方图")
+        logger.info(f"获取{area_name}的最新有效态Cd预测直方图")
         
         service = CdPredictionServiceV3()
         
-        # 验证县市是否支持
-        if not service.is_county_supported(county_name):
-            raise HTTPException(
-                status_code=404, 
-                detail=f"不支持的县市: {county_name}"
-            )
-        
         # 查找最新的直方图文件
-        histogram_pattern = f"prediction_histogram_effective_cd_{county_name}_*.jpg"
+        histogram_pattern = f"prediction_histogram_effective_cd_{area_name}_*.jpg"
         histogram_files = []
         
         # 在输出目录中查找相关文件
@@ -586,7 +550,7 @@ async def get_latest_effective_cd_histogram(county_name: str):
         if not histogram_files:
             raise HTTPException(
                 status_code=404, 
-                detail=f"未找到{county_name}的有效态Cd预测直方图,请先执行预测"
+                detail=f"未找到{area_name}的有效态Cd预测直方图,请先执行预测"
             )
         
         # 选择最新的文件(按修改时间排序)
@@ -597,370 +561,15 @@ async def get_latest_effective_cd_histogram(county_name: str):
         
         return FileResponse(
             path=latest_histogram,
-            filename=f"{county_name}_latest_effective_cd_histogram.jpg",
+            filename=f"{area_name}_latest_effective_cd_histogram.jpg",
             media_type="image/jpeg"
         )
         
     except HTTPException:
         raise
     except Exception as e:
-        logger.error(f"获取{county_name}最新有效态Cd直方图失败: {str(e)}")
+        logger.error(f"获取{area_name}最新有效态Cd直方图失败: {str(e)}")
         raise HTTPException(
             status_code=500, 
-            detail=f"获取{county_name}最新有效态Cd直方图失败: {str(e)}"
+            detail=f"获取{area_name}最新有效态Cd直方图失败: {str(e)}"
         )
-
-@router.get("/latest-results/{county_name}", 
-           summary="获取县市最新预测结果概览", 
-           description="获取指定县市所有最新预测结果的概览信息")
-async def get_latest_results_overview(county_name: str):
-    """
-    获取指定县市的最新预测结果概览
-    
-    @param county_name: 县市名称,如:乐昌市
-    @returns {Dict[str, Any]} 最新预测结果的概览信息
-    """
-    try:
-        logger.info(f"获取{county_name}的最新预测结果概览")
-        
-        service = CdPredictionServiceV3()
-        
-        # 验证县市是否支持
-        if not service.is_county_supported(county_name):
-            raise HTTPException(
-                status_code=404, 
-                detail=f"不支持的县市: {county_name}"
-            )
-        
-        import glob
-        output_dir = service.output_figures_dir
-        
-        # 查找所有相关文件
-        result_files = {
-            "crop_cd_maps": [],
-            "crop_cd_histograms": [],
-            "effective_cd_maps": [],
-            "effective_cd_histograms": []
-        }
-        
-        # 作物Cd地图
-        crop_map_pattern = os.path.join(output_dir, f"prediction_map_crop_cd_{county_name}_*.jpg")
-        result_files["crop_cd_maps"] = glob.glob(crop_map_pattern)
-        
-        # 作物Cd直方图
-        crop_hist_pattern = os.path.join(output_dir, f"prediction_histogram_crop_cd_{county_name}_*.jpg")
-        result_files["crop_cd_histograms"] = glob.glob(crop_hist_pattern)
-        
-        # 有效态Cd地图
-        eff_map_pattern = os.path.join(output_dir, f"prediction_map_effective_cd_{county_name}_*.jpg")
-        result_files["effective_cd_maps"] = glob.glob(eff_map_pattern)
-        
-        # 有效态Cd直方图
-        eff_hist_pattern = os.path.join(output_dir, f"prediction_histogram_effective_cd_{county_name}_*.jpg")
-        result_files["effective_cd_histograms"] = glob.glob(eff_hist_pattern)
-        
-        # 构建结果概览
-        overview = {
-            "county_name": county_name,
-            "available_results": {},
-            "latest_files": {},
-            "total_results": 0
-        }
-        
-        for result_type, files in result_files.items():
-            if files:
-                # 按修改时间排序,获取最新文件
-                latest_file = max(files, key=os.path.getmtime)
-                file_stats = os.stat(latest_file)
-                
-                overview["available_results"][result_type] = {
-                    "count": len(files),
-                    "latest_timestamp": file_stats.st_mtime,
-                    "latest_size": file_stats.st_size
-                }
-                
-                overview["latest_files"][result_type] = {
-                    "filename": os.path.basename(latest_file),
-                    "path": latest_file,
-                    "download_url": f"/api/cd-prediction/{result_type.replace('_', '-')}/latest-{'map' if 'map' in result_type else 'histogram'}/{county_name}"
-                }
-                
-                overview["total_results"] += len(files)
-        
-        if overview["total_results"] == 0:
-            raise HTTPException(
-                status_code=404, 
-                detail=f"未找到{county_name}的任何预测结果,请先执行预测"
-            )
-        
-        return {
-            "success": True,
-            "message": f"获取{county_name}预测结果概览成功",
-            "data": overview
-        }
-        
-    except HTTPException:
-        raise
-    except Exception as e:
-        logger.error(f"获取{county_name}预测结果概览失败: {str(e)}")
-        raise HTTPException(
-            status_code=500, 
-            detail=f"获取{county_name}预测结果概览失败: {str(e)}"
-        )
-
-# =============================================================================
-# 预测结果统计信息接口
-# =============================================================================
-
-@router.get("/crop-cd/statistics/{county_name}", 
-           summary="获取作物Cd预测统计信息", 
-           description="获取指定县市的作物Cd预测结果的详细统计信息")
-async def get_crop_cd_statistics(county_name: str):
-    """
-    获取指定县市的作物Cd预测统计信息
-    
-    @param county_name: 县市名称,如:乐昌市
-    @returns {Dict[str, Any]} 预测结果的统计信息
-    """
-    try:
-        logger.info(f"获取{county_name}的作物Cd预测统计信息")
-        
-        service = CdPredictionServiceV3()
-        
-        # 验证县市是否支持
-        if not service.is_county_supported(county_name):
-            raise HTTPException(
-                status_code=404, 
-                detail=f"不支持的县市: {county_name}"
-            )
-        
-        # 获取预测结果数据
-        stats = service.get_crop_cd_statistics(county_name)
-        
-        if not stats:
-            raise HTTPException(
-                status_code=404, 
-                detail=f"未找到{county_name}的作物Cd预测结果,请先执行预测"
-            )
-        
-        return {
-            "success": True,
-            "message": f"获取{county_name}作物Cd预测统计信息成功",
-            "data": stats
-        }
-        
-    except HTTPException:
-        raise
-    except Exception as e:
-        logger.error(f"获取{county_name}作物Cd预测统计信息失败: {str(e)}")
-        raise HTTPException(
-            status_code=500, 
-            detail=f"获取{county_name}作物Cd预测统计信息失败: {str(e)}"
-        )
-
-@router.get("/effective-cd/statistics/{county_name}", 
-           summary="获取有效态Cd预测统计信息", 
-           description="获取指定县市的有效态Cd预测结果的详细统计信息")
-async def get_effective_cd_statistics(county_name: str):
-    """
-    获取指定县市的有效态Cd预测统计信息
-    
-    @param county_name: 县市名称,如:乐昌市
-    @returns {Dict[str, Any]} 预测结果的统计信息
-    """
-    try:
-        logger.info(f"获取{county_name}的有效态Cd预测统计信息")
-        
-        service = CdPredictionServiceV3()
-        
-        # 验证县市是否支持
-        if not service.is_county_supported(county_name):
-            raise HTTPException(
-                status_code=404, 
-                detail=f"不支持的县市: {county_name}"
-            )
-        
-        # 获取预测结果数据
-        stats = service.get_effective_cd_statistics(county_name)
-        
-        if not stats:
-            raise HTTPException(
-                status_code=404, 
-                detail=f"未找到{county_name}的有效态Cd预测结果,请先执行预测"
-            )
-        
-        return {
-            "success": True,
-            "message": f"获取{county_name}有效态Cd预测统计信息成功",
-            "data": stats
-        }
-        
-    except HTTPException:
-        raise
-    except Exception as e:
-        logger.error(f"获取{county_name}有效态Cd预测统计信息失败: {str(e)}")
-        raise HTTPException(
-            status_code=500, 
-            detail=f"获取{county_name}有效态Cd预测统计信息失败: {str(e)}"
-        )
-
-@router.get("/combined-statistics/{county_name}", 
-           summary="获取综合预测统计信息", 
-           description="获取指定县市的作物Cd和有效态Cd预测结果的综合统计信息")
-async def get_combined_statistics(county_name: str):
-    """
-    获取指定县市的综合预测统计信息
-    
-    @param county_name: 县市名称,如:乐昌市
-    @returns {Dict[str, Any]} 综合预测结果的统计信息
-    """
-    try:
-        logger.info(f"获取{county_name}的综合预测统计信息")
-        
-        service = CdPredictionServiceV3()
-        
-        # 验证县市是否支持
-        if not service.is_county_supported(county_name):
-            raise HTTPException(
-                status_code=404, 
-                detail=f"不支持的县市: {county_name}"
-            )
-        
-        # 获取综合统计信息
-        stats = service.get_combined_statistics(county_name)
-        
-        if not stats:
-            raise HTTPException(
-                status_code=404, 
-                detail=f"未找到{county_name}的预测结果,请先执行预测"
-            )
-        
-        return {
-            "success": True,
-            "message": f"获取{county_name}综合预测统计信息成功",
-            "data": stats
-        }
-        
-    except HTTPException:
-        raise
-    except Exception as e:
-        logger.error(f"获取{county_name}综合预测统计信息失败: {str(e)}")
-        raise HTTPException(
-            status_code=500, 
-            detail=f"获取{county_name}综合预测统计信息失败: {str(e)}"
-        )
-
-
-
-@router.get("/all-statistics", 
-           summary="获取所有县市统计概览", 
-           description="获取所有支持县市的预测结果统计概览")
-async def get_all_statistics():
-    """
-    获取所有支持县市的预测结果统计概览
-    
-    @returns {Dict[str, Any]} 所有县市的统计概览
-    """
-    try:
-        logger.info("获取所有县市统计概览")
-        
-        service = CdPredictionServiceV3()
-        
-        # 获取所有县市的统计概览
-        all_stats = service.get_all_counties_statistics()
-        
-        return {
-            "success": True,
-            "message": "获取所有县市统计概览成功",
-            "data": all_stats
-        }
-        
-    except Exception as e:
-        logger.error(f"获取所有县市统计概览失败: {str(e)}")
-        raise HTTPException(
-            status_code=500, 
-            detail=f"获取所有县市统计概览失败: {str(e)}"
-        )
-
-
-
-# =============================================================================
-# 获取最终预测结果CSV文件接口
-# =============================================================================
-
-@router.get("/download-final-crop-cd-csv",
-            summary="下载作物Cd最终预测结果CSV文件",
-            description="获取系统最终的作物Cd预测结果CSV文件(Final_predictions_crop_cd.csv)")
-async def download_final_crop_cd_csv():
-    """
-    下载作物Cd最终预测结果CSV文件
-
-    @returns {FileResponse} CSV文件
-    """
-    try:
-        logger.info("下载作物Cd最终预测结果CSV文件")
-
-        service = CdPredictionServiceV3()
-
-        # 构建CSV文件路径(适配你的实际路径)
-        csv_path = "Cd_Prediction_Integrated_System/data/final/Final_predictions_crop_cd.csv"
-
-        if not os.path.exists(csv_path):
-            raise HTTPException(
-                status_code=404,
-                detail="未找到作物Cd最终预测结果文件,请先执行预测"
-            )
-
-        return FileResponse(
-            path=csv_path,
-            filename="Final_predictions_crop_cd.csv",
-            media_type="text/csv"
-        )
-
-    except HTTPException:
-        raise
-    except Exception as e:
-        logger.error(f"下载作物Cd最终预测结果CSV失败: {str(e)}")
-        raise HTTPException(
-            status_code=500,
-            detail=f"下载作物Cd最终预测结果CSV失败: {str(e)}"
-        )
-
-
-@router.get("/download-final-effective-cd-csv",
-            summary="下载有效态Cd最终预测结果CSV文件",
-            description="获取系统最终的有效态Cd预测结果CSV文件(Final_predictions_effective_cd.csv)")
-async def download_final_effective_cd_csv():
-    """
-    下载有效态Cd最终预测结果CSV文件
-
-    @returns {FileResponse} CSV文件
-    """
-    try:
-        logger.info("下载有效态Cd最终预测结果CSV文件")
-
-        service = CdPredictionServiceV3()
-
-        # 构建CSV文件路径
-        csv_path = "Cd_Prediction_Integrated_System/data/final/Final_predictions_effective_cd.csv"
-
-        if not os.path.exists(csv_path):
-            raise HTTPException(
-                status_code=404,
-                detail="未找到有效态Cd最终预测结果文件,请先执行预测"
-            )
-
-        return FileResponse(
-            path=csv_path,
-            filename="Final_predictions_effective_cd.csv",
-            media_type="text/csv"
-        )
-
-    except HTTPException:
-        raise
-    except Exception as e:
-        logger.error(f"下载有效态Cd最终预测结果CSV失败: {str(e)}")
-        raise HTTPException(
-            status_code=500,
-            detail=f"下载有效态Cd最终预测结果CSV失败: {str(e)}"
-        )

+ 4 - 0
main.py

@@ -1,4 +1,8 @@
 # 1. 先创建 FastAPI 应用实例(只创建一次)
+# 设置matplotlib使用非交互式后端,避免服务器环境的GUI问题
+import matplotlib
+matplotlib.use('Agg')
+
 from fastapi import FastAPI
 from fastapi.middleware.cors import CORSMiddleware  # 导入 CORS 模块