release: bump version to 0.70.0
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
release / images (push) Has been cancelled
ci / delivery (push) Has been cancelled

This commit is contained in:
linkong
2026-06-04 17:16:23 +08:00
parent acbbfdf9e2
commit 8c204717cd
78 changed files with 1762 additions and 703 deletions

View File

@@ -18,6 +18,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.enums import BGPStatus
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
@@ -25,7 +26,7 @@ from app.models.bgp_anomaly import BGPAnomaly
from app.models.bgp_incident import BGPIncident
from app.models.bgp_observation import BGPObservation
from app.models.collected_data import CollectedData
from app.models.vessel import AISSourceHealth, VesselPosition, VesselStatic
from app.models.vessel import AISSourceHealth, VesselCurrentState, VesselPosition, VesselStatic
from app.services.bgp_collectors import build_bgp_collector_coverage
from app.services.cable_graph import build_graph_from_data, CableGraph, haversine_distance
from app.services.compute_center_locations import (
@@ -47,11 +48,10 @@ from app.services.location.llm_fallback import (
from app.services.persistent_logs import record_system_log
from app.services.vessel_ais_aggregation import (
build_field_conflict_candidates,
count_unique_raw_vessel_mmsi,
get_aggregated_vessel,
get_aggregated_vessel_track,
get_aggregated_vessels,
get_aggregated_vessels_snapshot,
get_current_vessels_snapshot,
get_vessel_conflict_records,
get_vessel_raw_observations,
MAX_SNAPSHOT_LIMIT,
@@ -75,7 +75,6 @@ TERRAIN_TILE_BATCH_MAX_ITEMS = 128
TERRAIN_TILE_BATCH_CONCURRENCY = 16
_terrain_tile_cache: OrderedDict[tuple[int, int, int], tuple[bytes, str, dict[str, str]]] = OrderedDict()
VESSEL_NAME_FALLBACK_PATTERN = re.compile(r"^mmsi\s*\d+$", re.IGNORECASE)
VESSEL_SNAPSHOT_LEGACY_FALLBACK_ENABLED = True
SECONDS_PER_MINUTE = 60
BYTES_PER_MIB = 1024 * 1024
CABLE_CACHE_FRESH_SECONDS = 6 * 60 * SECONDS_PER_MINUTE
@@ -839,9 +838,14 @@ def convert_aggregated_vessels_to_geojson(vessels: List[dict[str, Any]]) -> Dict
continue
source_summary = {}
for source, summary in (vessel.get("source_summary") or {}).items():
latest_observed_at = summary.get("latest_observed_at")
source_summary[source] = {
**summary,
"latest_observed_at": to_iso8601_utc(summary.get("latest_observed_at")),
"latest_observed_at": (
to_iso8601_utc(latest_observed_at)
if isinstance(latest_observed_at, datetime)
else latest_observed_at
),
}
props = {
"mmsi": vessel["mmsi"],
@@ -1075,7 +1079,7 @@ async def build_vessel_snapshot_response(
db: AsyncSession,
*,
bbox: tuple[float, float, float, float] | None,
zoom: int | None,
zoom: float | None,
type_filter: str | None,
limit: int | None,
since_minutes: int = 60,
@@ -2340,58 +2344,35 @@ async def _load_raw_vessel_snapshot_features(
observed_since: datetime,
) -> tuple[list[dict[str, Any]], dict[str, Any]]:
if bbox is None:
aggregated_vessels = await get_aggregated_vessels(
db,
limit=limit,
observed_since=observed_since,
)
else:
aggregated_vessels = await get_aggregated_vessels_snapshot(
db,
bbox=bbox,
limit=limit,
observed_since=observed_since,
)
raw_geojson = convert_aggregated_vessels_to_geojson(aggregated_vessels)
raw_features = raw_geojson.get("features", [])
features = raw_features
legacy_features: list[dict[str, Any]] = []
legacy_fallback_used = False
if not raw_features and VESSEL_SNAPSHOT_LEGACY_FALLBACK_ENABLED:
legacy_features = await _load_legacy_vessel_snapshot_features(
db,
bbox=bbox,
limit=limit,
)
features, _merge_diagnostics = _merge_vessel_features(raw_features, legacy_features)
legacy_fallback_used = bool(legacy_features)
return [], {
"source": "vessel_current_state",
"current_state_count": 0,
"final_unique_mmsi": 0,
}
current_vessels = await get_current_vessels_snapshot(
db,
bbox=bbox,
limit=limit,
observed_since=observed_since,
)
features = convert_aggregated_vessels_to_geojson(current_vessels).get("features", [])
unique_mmsi = len(
{
key
for key in (_feature_mmsi_key(feature) for feature in features)
if key is not None
}
)
return features, {
"raw_feature_count": len(raw_features),
"raw_unique_mmsi": len(
{
key
for key in (_feature_mmsi_key(feature) for feature in raw_features)
if key is not None
}
),
"legacy_feature_count": len(legacy_features),
"legacy_backfilled_mmsi": len(
{
key
for key in (_feature_mmsi_key(feature) for feature in legacy_features)
if key is not None
}
),
"legacy_fallback_enabled": VESSEL_SNAPSHOT_LEGACY_FALLBACK_ENABLED,
"legacy_fallback_used": legacy_fallback_used,
"final_unique_mmsi": len(
{
key
for key in (_feature_mmsi_key(feature) for feature in features)
if key is not None
}
),
"source": "vessel_current_state",
"current_state_count": len(features),
"final_unique_mmsi": unique_mmsi,
"raw_feature_count": 0,
"raw_unique_mmsi": 0,
"legacy_feature_count": 0,
"legacy_backfilled_mmsi": 0,
"legacy_fallback_enabled": False,
"legacy_fallback_used": False,
}
@router.get("/vessels/custom-supplements")
@@ -2759,10 +2740,10 @@ async def _build_visualization_geo_summary(db: AsyncSession) -> dict[str, Any]:
compute_center_count = supercomputer_count + gpu_cluster_count
active_incident_result = await db.execute(
select(func.count(BGPIncident.id)).where(BGPIncident.status == "active"),
select(func.count(BGPIncident.id)).where(BGPIncident.status == BGPStatus.ACTIVE.value),
)
active_anomaly_result = await db.execute(
select(func.count(BGPAnomaly.id)).where(BGPAnomaly.status == "active"),
select(func.count(BGPAnomaly.id)).where(BGPAnomaly.status == BGPStatus.ACTIVE.value),
)
active_incident_count = int(active_incident_result.scalar() or 0)
active_anomaly_count = int(active_anomaly_result.scalar() or 0)
@@ -2783,21 +2764,14 @@ async def _build_visualization_geo_summary(db: AsyncSession) -> dict[str, Any]:
)
else:
bgp_collector_count = int(bgp_collector_scalar or 0)
raw_unique_window_hours = 24
raw_unique_mmsi = await count_unique_raw_vessel_mmsi(
db,
observed_since=datetime.now(UTC) - timedelta(hours=raw_unique_window_hours),
vessel_current_window_minutes = 60
vessel_current_result = await db.execute(
select(func.count(VesselCurrentState.mmsi)).where(
VesselCurrentState.observed_at
>= datetime.now(UTC) - timedelta(minutes=vessel_current_window_minutes)
)
)
legacy_unique_result = await db.execute(
select(func.count(func.distinct(VesselPosition.mmsi)))
)
legacy_unique_mmsi = int(legacy_unique_result.scalar() or 0)
legacy_fallback_active = (
VESSEL_SNAPSHOT_LEGACY_FALLBACK_ENABLED
and raw_unique_mmsi == 0
and legacy_unique_mmsi > 0
)
vessel_count = legacy_unique_mmsi if legacy_fallback_active else raw_unique_mmsi
vessel_count = int(vessel_current_result.scalar() or 0)
aisstream_health = await db.get(AISSourceHealth, "aisstream_vessels")
return {
@@ -2808,11 +2782,10 @@ async def _build_visualization_geo_summary(db: AsyncSession) -> dict[str, Any]:
"satellite_count": satellite_count,
"compute_center_count": compute_center_count,
"vessel_count": vessel_count,
"vessel_count_source": "legacy_fallback" if legacy_fallback_active else "raw_recent",
"vessel_legacy_fallback_enabled": VESSEL_SNAPSHOT_LEGACY_FALLBACK_ENABLED,
"vessel_raw_unique_mmsi": raw_unique_mmsi,
"vessel_raw_unique_window_hours": raw_unique_window_hours,
"vessel_legacy_unique_mmsi": legacy_unique_mmsi,
"vessel_count_source": "vessel_current_state",
"vessel_current_window_minutes": vessel_current_window_minutes,
"vessel_raw_unique_mmsi": 0,
"vessel_legacy_unique_mmsi": 0,
"aisstream_connection_state": aisstream_health.connection_state if aisstream_health else None,
"aisstream_last_seen_at": to_iso8601_utc(aisstream_health.last_seen_at) if aisstream_health else None,
"aisstream_message_rate": aisstream_health.message_rate if aisstream_health else None,