Spaces:
Sleeping
Sleeping
| """ | |
| TradeFlow AI — Vessel Validation Router (T-070) | |
| GET /api/v1/vessel/validate?vessel_name=...&voyage=... | |
| POST /api/v1/vessel/validate (full validation with batch context) | |
| """ | |
| from __future__ import annotations | |
| from typing import Annotated | |
| from fastapi import APIRouter, Depends, Query | |
| from pydantic import BaseModel | |
| from ..auth.dependencies import CurrentUser, RequireOperator, get_current_user | |
| router = APIRouter(prefix="/api/v1", tags=["vessel"]) | |
| class VesselValidateRequest(BaseModel): | |
| vessel_name: str | |
| voyage_number: str | None = None | |
| bl_date: str | None = None | |
| arrival_port: str | None = None | |
| batch_id: str | None = None | |
| async def validate_vessel_quick( | |
| vessel_name: str = Query(..., description="Vessel name to validate"), | |
| voyage: str | None = Query(None, description="Voyage number"), | |
| _: None = RequireOperator, | |
| ) -> dict: | |
| """Quick vessel lookup — checks AIS database for vessel confirmation.""" | |
| import asyncpg | |
| from ..config import settings | |
| from ..services.ceisa_auth import CEISAAuthClient # noqa: F401 (unused — intentional stub) | |
| db_url = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql://") | |
| try: | |
| conn = await asyncpg.connect(db_url) | |
| row = await conn.fetchrow( | |
| """ | |
| SELECT imo, vessel_name, latitude, longitude, speed_knots, timestamp | |
| FROM ais_vessel_positions | |
| WHERE LOWER(vessel_name) LIKE LOWER($1) | |
| ORDER BY timestamp DESC LIMIT 1 | |
| """, | |
| f"%{vessel_name}%", | |
| ) | |
| await conn.close() | |
| if row: | |
| return { | |
| "found": True, | |
| "vessel_name": row["vessel_name"], | |
| "imo": row["imo"], | |
| "last_position": { | |
| "lat": row["latitude"], | |
| "lon": row["longitude"], | |
| "speed_knots": row["speed_knots"], | |
| "timestamp": row["timestamp"].isoformat() if row["timestamp"] else None, | |
| }, | |
| } | |
| return {"found": False, "vessel_name": vessel_name} | |
| except Exception as e: | |
| return {"found": False, "error": str(e)} | |
| async def validate_vessel_full( | |
| body: VesselValidateRequest, | |
| user: Annotated[CurrentUser, Depends(get_current_user)], | |
| ) -> dict: | |
| """Full vessel validation with AIS + port lineup checks.""" | |
| from packages.agents.src.nodes.vessel_validation_agent import ( | |
| vessel_validate_node, # type: ignore | |
| ) | |
| # Build minimal state for the vessel_validate_node | |
| mock_state = { | |
| "batch_id": body.batch_id or "adhoc", | |
| "reconciled_fields": [{ | |
| "vessel_name": {"value": body.vessel_name, "confidence": 1.0}, | |
| "voyage_number": {"value": body.voyage_number, "confidence": 1.0} if body.voyage_number else None, | |
| "bl_date": {"value": body.bl_date, "confidence": 1.0} if body.bl_date else None, | |
| "port_discharge_code": {"value": body.arrival_port, "confidence": 1.0} if body.arrival_port else None, | |
| }], | |
| "preprocessed": [], | |
| } | |
| result = await vessel_validate_node(mock_state) | |
| return result.get("vessel_validation", {}) | |