113 lines
3.0 KiB
Python
113 lines
3.0 KiB
Python
import os
|
|
from urllib.parse import quote
|
|
|
|
import httpx
|
|
from dotenv import load_dotenv
|
|
|
|
from schemas import ShipmentPage, TransportVehicle
|
|
|
|
load_dotenv()
|
|
|
|
|
|
class LoadPlanToolConfigurationError(RuntimeError):
|
|
pass
|
|
|
|
|
|
class LoadPlanToolRequestError(RuntimeError):
|
|
pass
|
|
|
|
|
|
def _required_env(name: str) -> str:
|
|
value = os.getenv(name, "").strip()
|
|
if not value:
|
|
raise LoadPlanToolConfigurationError(f"Missing required environment variable: {name}")
|
|
return value
|
|
|
|
|
|
def _env_positive_float(name: str, default: float) -> float:
|
|
raw_value = os.getenv(name, str(default)).strip()
|
|
try:
|
|
parsed = float(raw_value)
|
|
except ValueError as exc:
|
|
raise LoadPlanToolConfigurationError(f"Environment variable {name} must be a number") from exc
|
|
|
|
if parsed <= 0:
|
|
raise LoadPlanToolConfigurationError(f"Environment variable {name} must be greater than 0")
|
|
return parsed
|
|
|
|
|
|
def _api_base_url() -> str:
|
|
return _required_env("LOAD_PLAN_API_HOST").rstrip("/")
|
|
|
|
|
|
def _api_headers() -> dict[str, str]:
|
|
return {
|
|
"Authorization": _required_env("LOAD_PLAN_AGENT_ACCESS_KEY"),
|
|
"Accept": "application/json",
|
|
}
|
|
|
|
|
|
def _build_client() -> httpx.AsyncClient:
|
|
return httpx.AsyncClient(
|
|
base_url=_api_base_url(),
|
|
headers=_api_headers(),
|
|
timeout=_env_positive_float("LOAD_PLAN_API_TIMEOUT_SECONDS", 20.0),
|
|
)
|
|
|
|
|
|
def _raise_for_response(response: httpx.Response) -> None:
|
|
try:
|
|
response.raise_for_status()
|
|
except httpx.HTTPStatusError as exc:
|
|
body = exc.response.text.strip()
|
|
detail = body or f"status={exc.response.status_code}"
|
|
raise LoadPlanToolRequestError(
|
|
f"Load-plan API request failed for {exc.request.method} {exc.request.url}: {detail}"
|
|
) from exc
|
|
|
|
|
|
async def get_transport_vehicle_by_license_plate(
|
|
*,
|
|
merchant_id: int,
|
|
license_plate: str,
|
|
) -> TransportVehicle:
|
|
"""Fetch a specific transport vehicle for one merchant by license plate."""
|
|
encoded_plate = quote(license_plate, safe="")
|
|
path = f"/api/v2/ai/transport-vehicles/{encoded_plate}/"
|
|
|
|
async with _build_client() as client:
|
|
response = await client.get(
|
|
path,
|
|
params={"merchant_id": merchant_id},
|
|
)
|
|
|
|
_raise_for_response(response)
|
|
return TransportVehicle.model_validate(response.json())
|
|
|
|
|
|
async def list_unshipped_shipments(
|
|
*,
|
|
merchant_id: int,
|
|
area: str,
|
|
limit: int | None = None,
|
|
offset: int | None = None,
|
|
) -> ShipmentPage:
|
|
"""List unshipped shipments for one merchant and one exact area."""
|
|
params: dict[str, int | str] = {
|
|
"merchant_id": merchant_id,
|
|
"area": area,
|
|
}
|
|
if limit is not None:
|
|
params["limit"] = limit
|
|
if offset is not None:
|
|
params["offset"] = offset
|
|
|
|
async with _build_client() as client:
|
|
response = await client.get(
|
|
"/api/v2/ai/shipments/unshipped/",
|
|
params=params,
|
|
)
|
|
|
|
_raise_for_response(response)
|
|
return ShipmentPage.model_validate(response.json())
|