133 lines
4.3 KiB
Python
133 lines
4.3 KiB
Python
"""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)
|