initial project setup with README and ignore

This commit is contained in:
2026-04-08 15:13:42 +05:30
commit 2d5688cb35
47 changed files with 7929 additions and 0 deletions

2
app/core/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
"""Core application components."""

63
app/core/arrow_utils.py Normal file
View File

@@ -0,0 +1,63 @@
"""
High-performance utilities using Apache Arrow and NumPy for geographic data.
Provides vectorized operations for distances and coordinate processing.
"""
import numpy as np
import pyarrow as pa
import pyarrow.parquet as pq
import logging
from typing import List, Dict, Any, Tuple
logger = logging.getLogger(__name__)
def calculate_haversine_matrix_vectorized(lats: np.ndarray, lons: np.ndarray) -> np.ndarray:
"""
Calculate an N x N distance matrix using the Haversine formula.
Fully vectorized using NumPy for O(N^2) speed improvement over Python loops.
"""
# Earth's radius in kilometers
R = 6371.0
# Convert degrees to radians
lats_rad = np.radians(lats)
lons_rad = np.radians(lons)
# Create meshgrids for pairwise differences
# lats.reshape(-1, 1) creates a column vector
# lats.reshape(1, -1) creates a row vector
# Subtracting them creates an N x N matrix of differences
dlat = lats_rad.reshape(-1, 1) - lats_rad.reshape(1, -1)
dlon = lons_rad.reshape(-1, 1) - lons_rad.reshape(1, -1)
# Haversine formula
a = np.sin(dlat / 2)**2 + np.cos(lats_rad.reshape(-1, 1)) * np.cos(lats_rad.reshape(1, -1)) * np.sin(dlon / 2)**2
c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
return R * c
def orders_to_arrow_table(orders: List[Dict[str, Any]]) -> pa.Table:
"""
Convert a list of order dictionaries to an Apache Arrow Table.
This enables zero-copy operations and efficient columnar storage.
"""
return pa.Table.from_pylist(orders)
def save_optimized_route_parquet(orders: List[Dict[str, Any]], filename: str):
"""
Save optimized route data to a Parquet file for high-speed analysis.
Useful for logging and historical simulation replays.
"""
try:
table = orders_to_arrow_table(orders)
pq.write_table(table, filename)
logger.info(f" Saved route data to Parquet: {filename}")
except Exception as e:
logger.error(f" Failed to save Parquet: {e}")
def load_route_parquet(filename: str) -> List[Dict[str, Any]]:
"""
Load route data from a Parquet file and return as a list of dicts.
"""
table = pq.read_table(filename)
return table.to_pylist()

26
app/core/constants.py Normal file
View File

@@ -0,0 +1,26 @@
"""API constants and configuration."""
# API Configuration
API_VERSION = "2.0.0"
API_TITLE = "Route Optimization API"
API_DESCRIPTION = "Professional API for delivery route optimization"
# Route Optimization Limits
MAX_DELIVERIES = 50
MIN_DELIVERIES = 1
# Coordinate Validation
MIN_LATITUDE = -90
MAX_LATITUDE = 90
MIN_LONGITUDE = -180
MAX_LONGITUDE = 180
# Algorithm Types
ALGORITHM_GREEDY = "greedy"
ALGORITHM_TSP = "tsp"
# Response Messages
MESSAGE_SUCCESS = "Route optimized successfully"
MESSAGE_VALIDATION_ERROR = "Request validation failed"
MESSAGE_INTERNAL_ERROR = "An unexpected error occurred"

View File

@@ -0,0 +1,112 @@
"""Professional exception handlers for the API."""
import logging
from fastapi import Request, status
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
from app.core.exceptions import APIException
from app.models.errors import ErrorResponse, ErrorDetail
logger = logging.getLogger(__name__)
async def api_exception_handler(request: Request, exc: APIException) -> JSONResponse:
"""Handle custom API exceptions."""
request_id = getattr(request.state, "request_id", None)
error_response = ErrorResponse(
success=False,
error=ErrorDetail(
field=exc.field,
message=exc.message,
code=exc.code
),
path=request.url.path,
request_id=request_id
)
logger.warning(f"API Exception: {exc.code} - {exc.message} (Request ID: {request_id})")
return JSONResponse(
status_code=exc.status_code,
content=error_response.model_dump(exclude_none=True)
)
async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
"""Handle HTTP exceptions."""
request_id = getattr(request.state, "request_id", None)
error_response = ErrorResponse(
success=False,
error=ErrorDetail(
message=exc.detail,
code="HTTP_ERROR"
),
path=request.url.path,
request_id=request_id
)
logger.warning(f"HTTP Exception: {exc.status_code} - {exc.detail} (Request ID: {request_id})")
return JSONResponse(
status_code=exc.status_code,
content=error_response.model_dump(exclude_none=True)
)
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
"""Handle validation errors with detailed field information."""
request_id = getattr(request.state, "request_id", None)
errors = exc.errors()
if errors:
first_error = errors[0]
field = ".".join(str(loc) for loc in first_error.get("loc", []))
message = first_error.get("msg", "Validation error")
else:
field = None
message = "Validation error"
error_response = ErrorResponse(
success=False,
error=ErrorDetail(
field=field,
message=message,
code="VALIDATION_ERROR"
),
path=request.url.path,
request_id=request_id
)
logger.warning(f"Validation Error: {message} (Field: {field}, Request ID: {request_id})")
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=error_response.model_dump(exclude_none=True)
)
async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse:
"""Handle unexpected exceptions."""
request_id = getattr(request.state, "request_id", None)
error_response = ErrorResponse(
success=False,
error=ErrorDetail(
message="An unexpected error occurred. Please try again later.",
code="INTERNAL_SERVER_ERROR"
),
path=request.url.path,
request_id=request_id
)
logger.error(f"Unexpected Error: {str(exc)} (Request ID: {request_id})", exc_info=True)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content=error_response.model_dump(exclude_none=True)
)

70
app/core/exceptions.py Normal file
View File

@@ -0,0 +1,70 @@
"""Custom exceptions for the API."""
from fastapi import HTTPException, status
class APIException(HTTPException):
"""Base API exception with structured error format."""
def __init__(
self,
status_code: int,
message: str,
field: str = None,
code: str = None,
detail: str = None
):
self.message = message
self.field = field
self.code = code or self._get_default_code(status_code)
super().__init__(status_code=status_code, detail=detail or message)
def _get_default_code(self, status_code: int) -> str:
"""Get default error code based on status code."""
codes = {
400: "BAD_REQUEST",
401: "UNAUTHORIZED",
403: "FORBIDDEN",
404: "NOT_FOUND",
409: "CONFLICT",
422: "VALIDATION_ERROR",
429: "RATE_LIMIT_EXCEEDED",
500: "INTERNAL_SERVER_ERROR",
503: "SERVICE_UNAVAILABLE"
}
return codes.get(status_code, "UNKNOWN_ERROR")
class ValidationError(APIException):
"""Validation error exception."""
def __init__(self, message: str, field: str = None):
super().__init__(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
message=message,
field=field,
code="VALIDATION_ERROR"
)
class NotFoundError(APIException):
"""Resource not found exception."""
def __init__(self, message: str = "Resource not found"):
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
message=message,
code="NOT_FOUND"
)
class RateLimitError(APIException):
"""Rate limit exceeded exception."""
def __init__(self, message: str = "Rate limit exceeded"):
super().__init__(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
message=message,
code="RATE_LIMIT_EXCEEDED"
)