cosmo/backend/app/jobs/predefined.py

238 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
Predefined Scheduled Tasks
All registered tasks for scheduled execution
"""
import logging
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.dialects.postgresql import insert
from app.jobs.registry import task_registry
from app.models.db.celestial_body import CelestialBody
from app.models.db.position import Position
from app.services.horizons import HorizonsService
logger = logging.getLogger(__name__)
@task_registry.register(
name="sync_solar_system_positions",
description="同步太阳系天体位置数据从NASA Horizons API获取指定天体的位置数据并保存到数据库",
category="data_sync",
parameters=[
{
"name": "body_ids",
"type": "array",
"description": "要同步的天体ID列表例如['10', '199', '299']。如果不指定,则同步所有活跃的太阳系天体",
"required": False,
"default": None
},
{
"name": "days",
"type": "integer",
"description": "同步天数,从今天开始向未来延伸的天数",
"required": False,
"default": 7
},
{
"name": "source",
"type": "string",
"description": "数据源标记,用于标识数据来源",
"required": False,
"default": "nasa_horizons_cron"
}
]
)
async def sync_solar_system_positions(
db: AsyncSession,
logger: logging.Logger,
params: Dict[str, Any]
) -> Dict[str, Any]:
"""
Sync solar system body positions from NASA Horizons
Args:
db: Database session
logger: Logger instance
params: Task parameters
- body_ids: List of body IDs to sync (optional, defaults to all active)
- days: Number of days to sync (default: 7)
- source: Source tag for the data (default: "nasa_horizons_cron")
Returns:
Summary of sync operation
"""
body_ids = params.get("body_ids")
days = params.get("days", 7)
source = params.get("source", "nasa_horizons_cron")
logger.info(f"Starting solar system position sync: days={days}, source={source}")
# Get list of bodies to sync
if body_ids:
# Use specified body IDs
result = await db.execute(
select(CelestialBody).where(
CelestialBody.id.in_(body_ids),
CelestialBody.is_active == True
)
)
bodies = result.scalars().all()
logger.info(f"Syncing {len(bodies)} specified bodies")
else:
# Get all active solar system bodies
# Typically solar system bodies include planets, dwarf planets, and major satellites
result = await db.execute(
select(CelestialBody).where(
CelestialBody.is_active == True,
CelestialBody.system_id == 1,
CelestialBody.type.in_(['planet', 'dwarf_planet', 'satellite'])
)
)
bodies = result.scalars().all()
logger.info(f"Syncing all {len(bodies)} active solar system bodies")
if not bodies:
logger.warning("No bodies found to sync")
return {
"success": True,
"bodies_synced": 0,
"total_positions": 0,
"message": "No bodies found"
}
# Initialize services
horizons = HorizonsService()
# Sync positions for each body
total_positions = 0
synced_bodies = []
failed_bodies = []
start_time = datetime.utcnow()
end_time = start_time + timedelta(days=days)
for body in bodies:
# Use savepoint for this body's operations
async with db.begin_nested(): # Creates a SAVEPOINT
try:
logger.debug(f"Fetching positions for {body.name} ({body.id})")
# Fetch positions from NASA Horizons
positions = await horizons.get_body_positions(
body_id=body.id,
start_time=start_time,
end_time=end_time,
step="1d" # Daily positions
)
# Save positions to database (upsert logic)
count = 0
for pos in positions:
# Use PostgreSQL's INSERT ... ON CONFLICT to handle duplicates
stmt = insert(Position).values(
body_id=body.id,
time=pos.time,
x=pos.x,
y=pos.y,
z=pos.z,
vx=getattr(pos, 'vx', None),
vy=getattr(pos, 'vy', None),
vz=getattr(pos, 'vz', None),
source=source
)
# On conflict (body_id, time), update the existing record
stmt = stmt.on_conflict_do_update(
index_elements=['body_id', 'time'],
set_={
'x': pos.x,
'y': pos.y,
'z': pos.z,
'vx': getattr(pos, 'vx', None),
'vy': getattr(pos, 'vy', None),
'vz': getattr(pos, 'vz', None),
'source': source
}
)
await db.execute(stmt)
count += 1
# Savepoint will auto-commit if no exception
total_positions += count
synced_bodies.append(body.name)
logger.debug(f"Saved {count} positions for {body.name}")
except Exception as e:
# Savepoint will auto-rollback on exception
logger.error(f"Failed to sync {body.name}: {str(e)}")
failed_bodies.append({"body": body.name, "error": str(e)})
# Continue to next body
# Summary
result = {
"success": len(failed_bodies) == 0,
"bodies_synced": len(synced_bodies),
"total_positions": total_positions,
"synced_bodies": synced_bodies,
"failed_bodies": failed_bodies,
"time_range": f"{start_time.date()} to {end_time.date()}",
"source": source
}
logger.info(f"Sync completed: {len(synced_bodies)} bodies, {total_positions} positions")
return result
@task_registry.register(
name="sync_celestial_events",
description="同步天体事件数据(预留功能,暂未实现)",
category="data_sync",
parameters=[
{
"name": "event_types",
"type": "array",
"description": "事件类型列表,如['eclipse', 'conjunction', 'opposition']",
"required": False,
"default": None
},
{
"name": "days_ahead",
"type": "integer",
"description": "向未来查询的天数",
"required": False,
"default": 30
}
]
)
async def sync_celestial_events(
db: AsyncSession,
logger: logging.Logger,
params: Dict[str, Any]
) -> Dict[str, Any]:
"""
Sync celestial events (PLACEHOLDER - NOT IMPLEMENTED YET)
This is a reserved task for future implementation.
It will sync astronomical events like eclipses, conjunctions, oppositions, etc.
Args:
db: Database session
logger: Logger instance
params: Task parameters
- event_types: Types of events to sync
- days_ahead: Number of days ahead to query
Returns:
Summary of sync operation
"""
logger.warning("sync_celestial_events is not implemented yet")
return {
"success": False,
"message": "This task is reserved for future implementation",
"events_synced": 0
}