238 lines
7.8 KiB
Python
238 lines
7.8 KiB
Python
"""
|
||
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
|
||
}
|