Spaces:
Sleeping
Sleeping
| """ | |
| query_sprfmo.py - SPRFMO 查询函数(最简稳定版) | |
| """ | |
| from pathlib import Path | |
| import pandas as pd | |
| # ============================================================ | |
| # 1. 路径配置 | |
| # ============================================================ | |
| DATA_DIR = Path(r"C:\Users\niniy\Desktop\PythonProject\SPRFMO南太平洋 5×5") | |
| # ============================================================ | |
| # 2. 字段映射(原始 Excel 字段 → 标准字段) | |
| # ============================================================ | |
| CATCH_MAP = { | |
| "Year": "year", | |
| "Flag": "country", | |
| "Latitude": "lat", | |
| "Longitude": "lon", | |
| "Species": "species", | |
| "NumVesels": "numvessels", | |
| "Harvest_kg": "catch", | |
| "Discard_kg": "discard", | |
| } | |
| EFFORT_MAP = { | |
| "Year": "year", | |
| "Fishery": "gear_type", | |
| "NumDays": "effort", | |
| "NumEvents": "num_events", | |
| "NumVessels": "numvessels", | |
| } | |
| # ============================================================ | |
| # 3. 国家代码映射 | |
| # ============================================================ | |
| COUNTRY_MAP = { | |
| "中国": "CHN", "China": "CHN", "CHN": "CHN", | |
| "日本": "JPN", "Japan": "JPN", "JPN": "JPN", | |
| "韩国": "KOR", "Korea": "KOR", "KOR": "KOR", | |
| "中国台湾": "TWN", "Taiwan": "TWN", "TWN": "TWN", | |
| "智利": "CHL", "Chile": "CHL", "CHL": "CHL", | |
| "秘鲁": "PER", "Peru": "PER", "PER": "PER", | |
| "俄罗斯": "RUS", "Russia": "RUS", "RUS": "RUS", | |
| "美国": "USA", "USA": "USA", "United States": "USA", | |
| "新西兰": "NZL", "New Zealand": "NZL", "NZL": "NZL", | |
| "澳大利亚": "AUS", "Australia": "AUS", "AUS": "AUS", | |
| } | |
| # ============================================================ | |
| # 4. 主查询入口 | |
| # ============================================================ | |
| def query_sprfmo(filters=None, group_by=None, metrics="catch", | |
| data_type="catch", output_format="markdown"): | |
| """SPRFMO 数据查询入口""" | |
| if filters is None: | |
| filters = {} | |
| if group_by is None: | |
| group_by = [] | |
| elif isinstance(group_by, str): | |
| group_by = [group_by] | |
| if isinstance(metrics, str): | |
| metrics = [metrics] | |
| warnings = [] | |
| print("\n" + "=" * 60) | |
| print(f"【query_sprfmo】data_type={data_type}") | |
| print(f" filters={filters}") | |
| print(f" group_by={group_by}, metrics={metrics}") | |
| print("=" * 60) | |
| # 诊断信息 | |
| print(f"\n📂 数据文件夹: {DATA_DIR}") | |
| print(f" 文件夹存在: {DATA_DIR.exists()}") | |
| if DATA_DIR.exists(): | |
| all_files = list(DATA_DIR.glob("*.xlsx")) | |
| print(f" 所有 Excel: {[f.name for f in all_files]}") | |
| if not all_files: | |
| warnings.append(f"⚠️ 文件夹下没有任何 .xlsx 文件") | |
| else: | |
| warnings.append(f"⚠️ 文件夹不存在: {DATA_DIR}") | |
| return _error_result(f"文件夹不存在: {DATA_DIR}", warnings) | |
| # 加载数据 | |
| if data_type == "catch": | |
| df, source = _load_catch_data() | |
| elif data_type == "effort": | |
| df, source = _load_effort_data() | |
| else: | |
| return _error_result(f"未知 data_type: {data_type}", warnings) | |
| # 字段翻译 | |
| df = _translate(df, data_type) | |
| print(f" 翻译后字段: {df.columns.tolist()}") | |
| # 国家代码翻译 | |
| if "country" in filters and filters["country"]: | |
| c = str(filters["country"]) | |
| translated = COUNTRY_MAP.get(c, c) | |
| filters["country"] = translated | |
| if translated != c: | |
| print(f" 国家翻译: '{c}' → '{translated}'") | |
| # 筛选 | |
| df = _apply_filters(df, filters, data_type) | |
| if len(df) == 0: | |
| warnings.append("⚠️ 筛选后无数据,请检查筛选条件") | |
| # 聚合 | |
| df = _aggregate(df, group_by, metrics, data_type) | |
| # 返回 | |
| return { | |
| "preview_markdown": df.to_markdown(index=False, floatfmt=".2f") if len(df) > 0 else "*(无数据)*", | |
| "records": df.to_dict(orient="records"), | |
| "csv_path": None, | |
| "excel_path": None, | |
| "summary": _build_summary(df, data_type), | |
| "source_files": [source], | |
| "warnings": warnings, | |
| "metadata": { | |
| "spatial_resolution": "5x5 degree", | |
| "time_resolution": "annual", | |
| "region": "South Pacific", | |
| "unit": "kg" if data_type == "catch" else "days", | |
| } | |
| } | |
| # ============================================================ | |
| # 5. 加载数据 | |
| # ============================================================ | |
| def _load_catch_data(): | |
| """加载捕捞量 Excel""" | |
| files = [] | |
| for kw in ["捕捞", "Catch", "catch"]: | |
| files.extend(DATA_DIR.glob(f"*{kw}*.xlsx")) | |
| files = list(set(files)) | |
| if not files: | |
| all_files = [f.name for f in DATA_DIR.glob("*.xlsx")] | |
| raise FileNotFoundError( | |
| f"找不到捕捞量文件(需含 '捕捞' 或 'Catch')\n" | |
| f"文件夹下所有 Excel: {all_files}" | |
| ) | |
| df = pd.read_excel(files[0]) | |
| print(f" ✅ 加载: {files[0].name} ({len(df)} 条)") | |
| return df, str(files[0]) | |
| def _load_effort_data(): | |
| """加载努力量 Excel""" | |
| files = [] | |
| for kw in ["努力", "Effort", "effort"]: | |
| files.extend(DATA_DIR.glob(f"*{kw}*.xlsx")) | |
| files = list(set(files)) | |
| if not files: | |
| all_files = [f.name for f in DATA_DIR.glob("*.xlsx")] | |
| raise FileNotFoundError( | |
| f"找不到努力量文件(需含 '努力' 或 'Effort')\n" | |
| f"文件夹下所有 Excel: {all_files}" | |
| ) | |
| df = pd.read_excel(files[0]) | |
| print(f" ✅ 加载: {files[0].name} ({len(df)} 条)") | |
| return df, str(files[0]) | |
| # ============================================================ | |
| # 6. 字段翻译 | |
| # ============================================================ | |
| def _translate(df, data_type): | |
| """原始字段 → 标准字段""" | |
| fmap = CATCH_MAP if data_type == "catch" else EFFORT_MAP | |
| used = {k: v for k, v in fmap.items() if k in df.columns} | |
| return df.rename(columns=used) | |
| # ============================================================ | |
| # 7. 筛选 | |
| # ============================================================ | |
| def _apply_filters(df, filters, data_type): | |
| n0 = len(df) | |
| if "year_start" in filters and filters["year_start"] is not None: | |
| df = df[df["year"] >= filters["year_start"]] | |
| if "year_end" in filters and filters["year_end"] is not None: | |
| df = df[df["year"] <= filters["year_end"]] | |
| if data_type == "catch": | |
| if "country" in filters and filters["country"]: | |
| c = str(filters["country"]).upper() | |
| df = df[df["country"].astype(str).str.upper() == c] | |
| if "species" in filters and filters["species"]: | |
| kw = filters["species"] | |
| df = df[df["species"].astype(str).str.contains(kw, case=False, na=False)] | |
| elif data_type == "effort": | |
| if "gear_type" in filters and filters["gear_type"]: | |
| kw = filters["gear_type"] | |
| df = df[df["gear_type"].astype(str).str.contains(kw, case=False, na=False)] | |
| print(f" 筛选: {n0} → {len(df)} 条") | |
| return df | |
| # ============================================================ | |
| # 8. 聚合 | |
| # ============================================================ | |
| def _aggregate(df, group_by, metrics, data_type): | |
| if not group_by: | |
| return df | |
| valid_g = [g for g in group_by if g in df.columns] | |
| if not valid_g: | |
| return df | |
| valid_m = [m for m in metrics if m in df.columns] | |
| if not valid_m: | |
| return df | |
| agg = {m: ("first" if m == "numvessels" else "sum") for m in valid_m} | |
| df2 = df.groupby(valid_g, as_index=False).agg(agg) | |
| print(f" 聚合: {len(df2)} 条") | |
| return df2 | |
| # ============================================================ | |
| # 9. 摘要 | |
| # ============================================================ | |
| def _build_summary(df, data_type): | |
| summary = { | |
| "data_type": data_type, | |
| "total_records": len(df), | |
| "data_source": "SPRFMO", | |
| } | |
| if "year" in df.columns and len(df) > 0: | |
| years = df["year"].dropna() | |
| if len(years) > 0: | |
| summary["year_range"] = f"{int(years.min())}-{int(years.max())}" | |
| if data_type == "catch" and "catch" in df.columns: | |
| summary["total_catch"] = float(df["catch"].sum()) | |
| elif data_type == "effort" and "effort" in df.columns: | |
| summary["total_effort"] = float(df["effort"].sum()) | |
| return summary | |
| def _error_result(msg, warnings=None): | |
| if warnings is None: | |
| warnings = [msg] | |
| return { | |
| "preview_markdown": f"**错误**: {msg}", | |
| "records": [], | |
| "csv_path": None, | |
| "excel_path": None, | |
| "summary": {"data_type": None, "total_records": 0}, | |
| "source_files": [], | |
| "warnings": warnings, | |
| "metadata": {}, | |
| } | |
| # ============================================================ | |
| # 10. 测试代码 | |
| # ============================================================ | |
| if __name__ == "__main__": | |
| print("\n" + "🧪" * 30) | |
| print("测试 query_sprfmo") | |
| print("🧪" * 30) | |
| # 测试 1 | |
| print("\n【测试 1】中国 2015-2020 年渔获量(按年)") | |
| print("-" * 60) | |
| r = query_sprfmo( | |
| filters={"country": "CHN", "year_start": 2015, "year_end": 2020}, | |
| group_by=["year"], | |
| metrics=["catch"], | |
| data_type="catch", | |
| ) | |
| print(r["preview_markdown"]) | |
| print("\n摘要:", r["summary"]) | |
| if r["warnings"]: | |
| print("警告:") | |
| for w in r["warnings"]: | |
| print(f" {w}") | |
| # 测试 2 | |
| print("\n\n【测试 2】2018 年努力量(按渔业类型)") | |
| print("-" * 60) | |
| r2 = query_sprfmo( | |
| filters={"year_start": 2018, "year_end": 2018}, | |
| group_by=["gear_type"], | |
| metrics=["effort"], | |
| data_type="effort", | |
| ) | |
| print(r2["preview_markdown"]) | |
| print("\n摘要:", r2["summary"]) | |
| if r2["warnings"]: | |
| print("警告:") | |
| for w in r2["warnings"]: | |
| print(f" {w}") | |
| print("\n" + "✅" * 30) | |
| print("测试完成") | |
| print("✅" * 30) |