"""v4 strategy + v5 conflict-promotion + enrichment APIs for vessel_ais.""" from __future__ import annotations from typing import Any from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.security import get_current_user from app.db.session import get_db from app.models.user import User from app.models.vessel import AISConflictRecord from app.services.vessel_aggregation_strategy import ( StrategyValidationError, load_strategy, reset_strategy, save_strategy, ) from app.services.vessel_enrichment import ( get_vessel_enrichment_bundle, upsert_vessel_media_enrichment, upsert_vessel_profile_enrichment, ) router = APIRouter() @router.get("/strategy") async def get_aggregation_strategy(db: AsyncSession = Depends(get_db)): return await load_strategy(db) @router.put("/strategy") async def put_aggregation_strategy( payload: dict[str, Any], current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): try: return await save_strategy(db, payload) except StrategyValidationError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.delete("/strategy") async def reset_aggregation_strategy( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): return await reset_strategy(db) @router.post("/conflicts/{mmsi}/{field}/promote-to-rule") async def promote_conflict_to_rule( mmsi: int, field: str, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Lift the current conflict resolution into a persistent strategy rule.""" result = await db.execute( select(AISConflictRecord) .where(AISConflictRecord.target_schema == "vessel_ais") .where(AISConflictRecord.entity_key == str(mmsi)) .where(AISConflictRecord.field == field) .order_by(AISConflictRecord.updated_at.desc(), AISConflictRecord.id.desc()) .limit(1) ) record = result.scalar_one_or_none() if record is None or not record.selected_source: raise HTTPException(status_code=404, detail="Conflict record with selected_source not found") strategy = await load_strategy(db) vessel_ais = dict(strategy.get("vessel_ais") or {}) field_rules = dict(vessel_ais.get("field_rules") or {}) field_rules[field] = {"mode": "source_priority", "source_priority": [record.selected_source]} vessel_ais["field_rules"] = field_rules incoming = {"version": int(strategy.get("version") or 0), "vessel_ais": vessel_ais} try: return await save_strategy(db, incoming) except StrategyValidationError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.delete("/conflicts/{mmsi}/{field}/promote-to-rule") async def revert_conflict_rule( mmsi: int, field: str, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): strategy = await load_strategy(db) vessel_ais = dict(strategy.get("vessel_ais") or {}) field_rules = dict(vessel_ais.get("field_rules") or {}) if field in field_rules: del field_rules[field] vessel_ais["field_rules"] = field_rules incoming = {"version": int(strategy.get("version") or 0), "vessel_ais": vessel_ais} try: return await save_strategy(db, incoming) except StrategyValidationError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @router.get("/enrichment/{mmsi}") async def get_vessel_enrichment(mmsi: int, db: AsyncSession = Depends(get_db)): return await get_vessel_enrichment_bundle(db, mmsi) @router.put("/enrichment/{mmsi}/profile") async def put_vessel_profile_enrichment( mmsi: int, payload: dict[str, Any], current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): return await upsert_vessel_profile_enrichment(db, mmsi=mmsi, payload=payload) @router.put("/enrichment/{mmsi}/media") async def put_vessel_media_enrichment( mmsi: int, payload: dict[str, Any], current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): return await upsert_vessel_media_enrichment(db, mmsi=mmsi, payload=payload)