release: bump version to 0.36.0

This commit is contained in:
rayd1o
2026-04-22 23:42:10 +08:00
parent 6a5f9f7ad4
commit abe04030fb
23 changed files with 1875 additions and 27 deletions

View File

@@ -13,6 +13,7 @@ from sqlalchemy import select, func
from typing import List, Dict, Any, Optional
from app.core.collected_data_fields import get_record_field
from app.core.countries import get_country_centroid
from app.core.satellite_tle import build_tle_lines_from_elements
from app.core.time import to_iso8601_utc
from app.db.session import get_db
@@ -363,6 +364,215 @@ def convert_gpu_cluster_to_geojson(records: List[CollectedData]) -> Dict[str, An
return {"type": "FeatureCollection", "features": features}
def _parse_float(value: Any) -> Optional[float]:
try:
if value in (None, ""):
return None
return float(value)
except (TypeError, ValueError):
return None
COMPUTE_CENTER_COORDINATE_HINTS = (
("el capitan", 37.6819, -121.7681),
("livermore", 37.6819, -121.7681),
("llnl", 37.6819, -121.7681),
("lawrence livermore", 37.6819, -121.7681),
("frontier", 35.9319, -84.3107),
("oak ridge", 35.9319, -84.3107),
("ornl", 35.9319, -84.3107),
("aurora", 41.7130, -87.9820),
("argonne", 41.7130, -87.9820),
("anl", 41.7130, -87.9820),
("fugaku", 34.6953, 135.1974),
("kobe", 34.6953, 135.1974),
("riken", 34.6953, 135.1974),
("summit", 35.9319, -84.3107),
("leonardo", 44.4949, 11.3426),
("bologna", 44.4949, 11.3426),
("alps", 46.0037, 8.9511),
("lugano", 46.0037, 8.9511),
("sunway taihulight", 31.4912, 120.3119),
("wuxi", 31.4912, 120.3119),
("tianhe-2", 23.1291, 113.2644),
("tianhe-2a", 23.1291, 113.2644),
("guangzhou", 23.1291, 113.2644),
("colossus", 35.1495, -90.0490),
("memphis", 35.1495, -90.0490),
("xai", 35.1495, -90.0490),
)
def _normalize_hint_text(*parts: Any) -> str:
return " ".join(
str(part).strip().lower()
for part in parts
if part not in (None, "")
)
def _resolve_compute_center_coordinates(
record: CollectedData,
metadata: Dict[str, Any],
) -> Dict[str, Any]:
latitude = _parse_float(get_record_field(record, "latitude"))
longitude = _parse_float(get_record_field(record, "longitude"))
if latitude not in (None, 0.0) and longitude not in (None, 0.0):
return {
"latitude": latitude,
"longitude": longitude,
"location_precision": "precise",
"geography_mode": "source_coordinates",
"is_estimated": False,
"estimated_reason": None,
}
hint_text = _normalize_hint_text(
record.name,
get_record_field(record, "city"),
get_record_field(record, "country"),
metadata.get("site"),
metadata.get("organization"),
metadata.get("operator"),
)
for needle, resolved_latitude, resolved_longitude in COMPUTE_CENTER_COORDINATE_HINTS:
if needle in hint_text:
return {
"latitude": resolved_latitude,
"longitude": resolved_longitude,
"location_precision": "estimated_site",
"geography_mode": "site_hint",
"is_estimated": True,
"estimated_reason": f"Matched known site hint: {needle}",
}
centroid = get_country_centroid(get_record_field(record, "country"))
if centroid:
return {
"latitude": centroid.get("latitude"),
"longitude": centroid.get("longitude"),
"location_precision": "estimated_country",
"geography_mode": "country_centroid",
"is_estimated": True,
"estimated_reason": "Estimated from country centroid",
}
return {
"latitude": latitude,
"longitude": longitude,
"location_precision": "unknown",
"geography_mode": "unknown",
"is_estimated": True,
"estimated_reason": "No resolvable location hints",
}
def _normalize_capacity_band(capacity_value: Optional[float], capacity_unit: str) -> str:
if capacity_value is None:
return "unknown"
unit = str(capacity_unit or "").strip().lower()
if unit in {"pflop/s", "pflops", "pflop"}:
normalized_tflops = capacity_value * 1000
elif unit in {"gflop/s", "gflops", "gflop"}:
normalized_tflops = capacity_value / 1000
else:
normalized_tflops = capacity_value
if normalized_tflops >= 1_000_000:
return "exascale"
if normalized_tflops >= 100_000:
return "ultra"
if normalized_tflops >= 10_000:
return "large"
if normalized_tflops > 0:
return "regional"
return "unknown"
def convert_compute_centers_to_geojson(records: List[CollectedData]) -> Dict[str, Any]:
"""Convert compute infrastructure records into a unified GeoJSON layer."""
features = []
for record in records:
metadata = record.extra_data or {}
coordinate_info = _resolve_compute_center_coordinates(record, metadata)
latitude = coordinate_info.get("latitude")
longitude = coordinate_info.get("longitude")
site_type = (
"supercomputer"
if record.source == "top500" or record.data_type == "supercomputer"
else "gpu_cluster"
)
if latitude in (None, 0.0) or longitude in (None, 0.0):
continue
if site_type == "supercomputer":
capacity_value = _parse_float(get_record_field(record, "rmax"))
capacity_unit = "GFlops"
else:
capacity_value = _parse_float(get_record_field(record, "value"))
capacity_unit = str(get_record_field(record, "unit") or "TFlop/s")
vendor = (
metadata.get("manufacturer")
or metadata.get("vendor")
or metadata.get("gpu_type")
)
operator = (
metadata.get("organization")
or metadata.get("operator")
or metadata.get("owner")
)
rank = metadata.get("rank")
if rank in (None, "") and site_type == "supercomputer":
rank = get_record_field(record, "rank")
updated_at = to_iso8601_utc(record.reference_date or record.collected_at)
features.append(
{
"type": "Feature",
"id": record.id,
"geometry": {
"type": "Point",
"coordinates": [longitude or 0, latitude or 0],
},
"properties": {
"id": record.id,
"source_id": record.source_id,
"name": record.name,
"site_type": site_type,
"country": get_record_field(record, "country"),
"city": get_record_field(record, "city"),
"latitude": latitude,
"longitude": longitude,
"operator": operator,
"vendor": vendor,
"capacity_value": capacity_value,
"capacity_unit": capacity_unit,
"capacity_band": _normalize_capacity_band(capacity_value, capacity_unit),
"rank": rank,
"gpu_count": metadata.get("gpu_count"),
"gpu_type": metadata.get("gpu_type"),
"cores": get_record_field(record, "cores"),
"power": get_record_field(record, "power"),
"source": record.source,
"updated_at": updated_at,
"status": "observed",
"location_precision": coordinate_info.get("location_precision"),
"geography_mode": coordinate_info.get("geography_mode"),
"is_estimated": coordinate_info.get("is_estimated", False),
"estimated_reason": coordinate_info.get("estimated_reason"),
"data_type": "compute_center",
"metadata": metadata,
},
}
)
return {"type": "FeatureCollection", "features": features}
def convert_bgp_anomalies_to_geojson(
records: List[BGPAnomaly],
geography_hints: Optional[Dict[str, Dict[str, Any]]] = None,
@@ -975,6 +1185,53 @@ async def get_gpu_clusters_geojson(
}
@router.get("/geo/compute-centers")
async def get_compute_centers_geojson(
limit: int = Query(200, ge=1, le=1000),
db: AsyncSession = Depends(get_db),
):
"""获取统一算力中心 GeoJSON 数据"""
records_by_source = await _load_current_collected_data_by_sources(
db,
["top500", "epoch_ai_gpu"],
)
records = _filter_known_records(
records_by_source.get("top500", []) + records_by_source.get("epoch_ai_gpu", []),
)
if limit is not None:
records = records[:limit]
if not records:
return {
"type": "FeatureCollection",
"features": [],
"count": 0,
"stats": {
"total": 0,
"supercomputers": 0,
"gpu_clusters": 0,
},
}
geojson = convert_compute_centers_to_geojson(records)
features = geojson.get("features", [])
return {
**geojson,
"count": len(features),
"stats": {
"total": len(features),
"supercomputers": sum(
1 for feature in features
if feature.get("properties", {}).get("site_type") == "supercomputer"
),
"gpu_clusters": sum(
1 for feature in features
if feature.get("properties", {}).get("site_type") == "gpu_cluster"
),
},
}
@router.get("/geo/bgp-anomalies")
async def get_bgp_anomalies_geojson(
severity: Optional[str] = Query(None),

View File

@@ -0,0 +1,217 @@
from datetime import datetime, timezone
import pytest
from httpx import ASGITransport, AsyncClient
from app.api.v1.visualization import convert_compute_centers_to_geojson
from app.db.session import get_db
from app.main import app
from app.models.collected_data import CollectedData
def _build_record(
*,
record_id: int,
source: str,
data_type: str,
name: str,
country: str,
city: str,
latitude: float,
longitude: float,
metadata: dict,
):
return CollectedData(
id=record_id,
source=source,
data_type=data_type,
source_id=f"{source}-{record_id}",
name=name,
extra_data={
"country": country,
"city": city,
"latitude": latitude,
"longitude": longitude,
**metadata,
},
collected_at=datetime(2026, 4, 22, tzinfo=timezone.utc),
reference_date=datetime(2026, 4, 21, tzinfo=timezone.utc),
is_current=True,
)
def test_convert_compute_centers_to_geojson_unifies_sources():
top500_record = _build_record(
record_id=1,
source="top500",
data_type="supercomputer",
name="Frontier",
country="United States",
city="Oak Ridge",
latitude=35.93,
longitude=-84.31,
metadata={
"rank": 1,
"manufacturer": "HPE",
"organization": "ORNL",
"rmax": 1102000.0,
"cores": 8730112,
"power": 21510.0,
},
)
gpu_record = _build_record(
record_id=2,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Colossus",
country="United States",
city="Memphis",
latitude=35.15,
longitude=-90.05,
metadata={
"organization": "xAI",
"gpu_type": "H100",
"gpu_count": 100000,
"value": "20000",
"unit": "TFlop/s",
},
)
payload = convert_compute_centers_to_geojson([top500_record, gpu_record])
assert payload["type"] == "FeatureCollection"
assert len(payload["features"]) == 2
supercomputer_feature = payload["features"][0]
assert supercomputer_feature["properties"]["site_type"] == "supercomputer"
assert supercomputer_feature["properties"]["capacity_unit"] == "GFlops"
assert supercomputer_feature["properties"]["capacity_band"] == "exascale"
assert supercomputer_feature["properties"]["operator"] == "ORNL"
assert supercomputer_feature["properties"]["location_precision"] == "precise"
assert supercomputer_feature["properties"]["is_estimated"] is False
gpu_feature = payload["features"][1]
assert gpu_feature["properties"]["site_type"] == "gpu_cluster"
assert gpu_feature["properties"]["vendor"] == "H100"
assert gpu_feature["properties"]["gpu_count"] == 100000
assert gpu_feature["properties"]["capacity_band"] == "large"
assert gpu_feature["properties"]["location_precision"] == "precise"
def test_convert_compute_centers_to_geojson_uses_coordinate_hints():
hinted_record = _build_record(
record_id=3,
source="top500",
data_type="supercomputer",
name="Frontier",
country="United States",
city="",
latitude=0.0,
longitude=0.0,
metadata={
"organization": "Oak Ridge National Laboratory",
"rmax": 1102000.0,
},
)
payload = convert_compute_centers_to_geojson([hinted_record])
assert len(payload["features"]) == 1
coords = payload["features"][0]["geometry"]["coordinates"]
assert coords[0] == pytest.approx(-84.3107)
assert coords[1] == pytest.approx(35.9319)
assert payload["features"][0]["properties"]["is_estimated"] is True
assert payload["features"][0]["properties"]["location_precision"] == "estimated_site"
def test_convert_compute_centers_to_geojson_falls_back_to_country_centroid():
centroid_record = _build_record(
record_id=4,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Unknown Cluster",
country="United States",
city="",
latitude=0.0,
longitude=0.0,
metadata={
"organization": "Unknown Operator",
"value": "10000",
"unit": "TFlop/s",
},
)
payload = convert_compute_centers_to_geojson([centroid_record])
assert len(payload["features"]) == 1
props = payload["features"][0]["properties"]
coords = payload["features"][0]["geometry"]["coordinates"]
assert coords[0] == pytest.approx(-98.5795)
assert coords[1] == pytest.approx(39.8283)
assert props["is_estimated"] is True
assert props["location_precision"] == "estimated_country"
assert props["geography_mode"] == "country_centroid"
@pytest.mark.asyncio
async def test_compute_centers_geojson_endpoint_returns_stats():
records = [
_build_record(
record_id=1,
source="top500",
data_type="supercomputer",
name="Frontier",
country="United States",
city="Oak Ridge",
latitude=35.93,
longitude=-84.31,
metadata={"rank": 1, "rmax": 1102000.0},
),
_build_record(
record_id=2,
source="epoch_ai_gpu",
data_type="gpu_cluster",
name="Colossus",
country="United States",
city="Memphis",
latitude=35.15,
longitude=-90.05,
metadata={"value": "20000", "unit": "TFlop/s"},
),
]
class _ScalarResult:
def __init__(self, rows):
self._rows = rows
def scalars(self):
class _Scalars:
def __init__(self, rows):
self._rows = rows
def all(self):
return self._rows
return _Scalars(self._rows)
class _FakeSession:
async def execute(self, _query):
return _ScalarResult(records)
async def override_get_db():
yield _FakeSession()
app.dependency_overrides[get_db] = override_get_db
transport = ASGITransport(app=app)
try:
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.get("/api/v1/visualization/geo/compute-centers")
assert response.status_code == 200
data = response.json()
assert data["count"] == 2
assert data["stats"]["supercomputers"] == 1
assert data["stats"]["gpu_clusters"] == 1
assert data["features"][0]["properties"]["data_type"] == "compute_center"
finally:
app.dependency_overrides.clear()