feat: Mount LoRRI integration router in main app + always init DB + open CORS for production

#11
by MouleeswaranM - opened
Files changed (1) hide show
  1. brain/app/main.py +57 -42
brain/app/main.py CHANGED
@@ -47,7 +47,7 @@ async def lifespan(app: FastAPI):
47
  # Startup
48
  logger.info(f"Starting {settings.app_title} v{settings.app_version} (env={settings.app_env})")
49
 
50
- # Always initialize the database (creates tables for SQLite fallback)
51
  try:
52
  from app.database import init_db, check_db_health
53
  await init_db()
@@ -57,7 +57,7 @@ async def lifespan(app: FastAPI):
57
  else:
58
  logger.warning("Database init succeeded but health check failed")
59
  except Exception as e:
60
- logger.warning(f"Database initialization failed - running without persistence: {e}")
61
 
62
  yield
63
  # Shutdown
@@ -69,26 +69,24 @@ app = FastAPI(
69
  title=settings.app_title,
70
  version=settings.app_version,
71
  description="""
72
- ## Fair Dispatch System API
73
-
74
- A fairness-focused route allocation system for delivery operations.
75
-
76
- ### Features
77
- - **Route Clustering**: Groups packages using K-Means for efficient routes
78
- - **Workload Scoring**: Calculates balanced workload metrics
79
- - **Fairness Metrics**: Computes Gini index and fairness scores
80
- - **Explainability**: Provides human-readable explanations for allocations
81
- - **LangGraph Workflow**: Multi-agent orchestration with LangSmith tracing
82
-
83
- ### Main Endpoints
84
- - `POST /api/v1/allocate` - Allocate packages to drivers (original)
85
- - `POST /api/v1/allocate/langgraph` - Allocate with LangGraph workflow
86
- - `POST /api/v1/consolidate` - AI Load Consolidation (5-agent LangGraph pipeline)
87
- - `POST /api/v1/consolidate/simulate` - Compare consolidation scenarios
88
- - `GET /api/v1/drivers/{id}` - Get driver details and stats
89
- - `GET /api/v1/routes/{id}` - Get route details
90
- - `POST /api/v1/feedback` - Submit driver feedback
91
- - `GET /api/v1/agent-events/stream` - SSE stream for agent events
92
  """,
93
  lifespan=lifespan,
94
  docs_url="/docs",
@@ -96,7 +94,7 @@ app = FastAPI(
96
  )
97
 
98
 
99
- # Global exception handler - prevent stack trace leaks in production
100
  @app.exception_handler(Exception)
101
  async def global_exception_handler(request: Request, exc: Exception):
102
  logger.error(f"Unhandled error on {request.method} {request.url.path}: {exc}", exc_info=True)
@@ -106,52 +104,70 @@ async def global_exception_handler(request: Request, exc: Exception):
106
  )
107
 
108
 
109
- # Add CORS middleware with wide origins for demo
110
  app.add_middleware(
111
  CORSMiddleware,
112
- allow_origins=["*"], # Allow all origins for demo/development
113
  allow_credentials=True,
114
  allow_methods=["*"],
115
  allow_headers=["*"],
116
  )
117
 
118
- # Include API routers
 
 
119
  app.include_router(allocation_router, prefix=settings.api_prefix)
120
  app.include_router(allocation_langgraph_router, prefix=settings.api_prefix)
 
 
121
  app.include_router(drivers_router, prefix=settings.api_prefix)
122
  app.include_router(routes_router, prefix=settings.api_prefix)
123
  app.include_router(feedback_router, prefix=settings.api_prefix)
124
  app.include_router(driver_api_router, prefix=settings.api_prefix)
 
 
125
  app.include_router(admin_router, prefix=settings.api_prefix)
126
  app.include_router(admin_learning_router, prefix=settings.api_prefix)
 
 
127
  app.include_router(consolidation_router, prefix=settings.api_prefix)
128
 
129
- # Include SSE agent events router (no prefix - it defines its own)
130
  app.include_router(agent_events_router)
131
 
132
- # Include run-scoped endpoints
133
  app.include_router(runs_router, prefix=settings.api_prefix)
134
 
 
 
 
 
 
 
 
 
 
 
135
 
136
  @app.get("/", tags=["Health"])
137
  async def root():
138
- """Root endpoint - health check."""
139
  return {
140
  "status": "healthy",
141
  "service": settings.app_title,
142
  "version": settings.app_version,
143
  "docs": "/docs",
 
144
  }
145
 
146
 
147
  @app.get("/health", tags=["Health"])
148
  async def health_check():
149
- """Health check endpoint with actual DB verification."""
150
  from app.database import check_db_health
151
  db_ok = await check_db_health()
152
- status_str = "healthy" if db_ok else "degraded"
153
  return {
154
- "status": status_str,
155
  "database": "connected" if db_ok else "disconnected",
156
  "version": settings.app_version,
157
  }
@@ -159,7 +175,7 @@ async def health_check():
159
 
160
  @app.get("/api/v1/health", tags=["Health"])
161
  async def api_health():
162
- """API health check for frontend connectivity tests."""
163
  from app.database import check_db_health
164
  db_ok = await check_db_health()
165
  return {
@@ -168,10 +184,12 @@ async def api_health():
168
  "version": settings.app_version,
169
  "agents": ["ml_effort", "route_planner", "fairness_manager", "driver_liaison", "final_resolution", "explainability"],
170
  "langgraph": True,
 
171
  }
172
 
173
 
174
- # Mount static files and demo endpoints
 
175
  if FRONTEND_DIR.exists():
176
  app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR)), name="static")
177
 
@@ -179,18 +197,15 @@ if FRONTEND_DIR.exists():
179
 
180
  @app.get("/demo/allocate", tags=["Demo"])
181
  async def demo_allocate():
182
- """Serve the API demo page for testing allocation endpoint."""
183
- demo_path = FRONTEND_DIR / "demo.html"
184
- return FileResponse(demo_path, media_type="text/html", headers=NO_CACHE)
185
 
186
  @app.get("/demo/visualization", tags=["Demo"])
187
  async def demo_visualization():
188
  """Serve the agent visualization page."""
189
- viz_path = FRONTEND_DIR / "visualization.html"
190
- return FileResponse(viz_path, media_type="text/html", headers=NO_CACHE)
191
 
192
  @app.get("/demo/consolidation", tags=["Demo"])
193
  async def demo_consolidation():
194
- """Serve the 5-agent load consolidation pipeline visualization."""
195
- path = FRONTEND_DIR / "consolidation.html"
196
- return FileResponse(path, media_type="text/html", headers=NO_CACHE)
 
47
  # Startup
48
  logger.info(f"Starting {settings.app_title} v{settings.app_version} (env={settings.app_env})")
49
 
50
+ # Always initialize DB (creates tables for SQLite fallback)
51
  try:
52
  from app.database import init_db, check_db_health
53
  await init_db()
 
57
  else:
58
  logger.warning("Database init succeeded but health check failed")
59
  except Exception as e:
60
+ logger.warning(f"Database initialization failed - running degraded: {e}")
61
 
62
  yield
63
  # Shutdown
 
69
  title=settings.app_title,
70
  version=settings.app_version,
71
  description="""
72
+ ## FairRelay β€” AI-Powered Fair Dispatch System
73
+
74
+ Production API for fairness-focused route allocation in logistics.
75
+ Integrates with LoRRI TMS (logisticsnow.in) as an AI intelligence layer.
76
+
77
+ ### Architecture: 6-Agent LangGraph Pipeline
78
+ 1. **ML Effort Agent** β€” Computes driver-route effort matrix
79
+ 2. **Route Planner** β€” OR-Tools optimal assignment
80
+ 3. **Fairness Manager** β€” Gini index evaluation, may trigger re-optimization
81
+ 4. **Driver Liaison** β€” Per-driver negotiation (accept/counter)
82
+ 5. **Final Resolution** β€” Resolves counter-proposals via swaps
83
+ 6. **Explainability** β€” Human-readable allocation explanations
84
+
85
+ ### LoRRI Integration
86
+ - `POST /lorri/allocate` β€” Production endpoint with API key auth
87
+ - `POST /lorri/wellness` β€” Driver wellness scoring
88
+ - `GET /lorri/health` β€” Integration health monitoring
89
+ - Webhook callbacks on allocation completion
 
 
90
  """,
91
  lifespan=lifespan,
92
  docs_url="/docs",
 
94
  )
95
 
96
 
97
+ # Global exception handler
98
  @app.exception_handler(Exception)
99
  async def global_exception_handler(request: Request, exc: Exception):
100
  logger.error(f"Unhandled error on {request.method} {request.url.path}: {exc}", exc_info=True)
 
104
  )
105
 
106
 
107
+ # CORS β€” allow all for demo/hackathon, restrict in production
108
  app.add_middleware(
109
  CORSMiddleware,
110
+ allow_origins=["*"],
111
  allow_credentials=True,
112
  allow_methods=["*"],
113
  allow_headers=["*"],
114
  )
115
 
116
+ # ═══ API Routers ══════════════════════════════════════════════════════════════
117
+
118
+ # Core allocation
119
  app.include_router(allocation_router, prefix=settings.api_prefix)
120
  app.include_router(allocation_langgraph_router, prefix=settings.api_prefix)
121
+
122
+ # Resources
123
  app.include_router(drivers_router, prefix=settings.api_prefix)
124
  app.include_router(routes_router, prefix=settings.api_prefix)
125
  app.include_router(feedback_router, prefix=settings.api_prefix)
126
  app.include_router(driver_api_router, prefix=settings.api_prefix)
127
+
128
+ # Admin
129
  app.include_router(admin_router, prefix=settings.api_prefix)
130
  app.include_router(admin_learning_router, prefix=settings.api_prefix)
131
+
132
+ # Consolidation (5-agent pipeline)
133
  app.include_router(consolidation_router, prefix=settings.api_prefix)
134
 
135
+ # SSE agent events
136
  app.include_router(agent_events_router)
137
 
138
+ # Run-scoped endpoints
139
  app.include_router(runs_router, prefix=settings.api_prefix)
140
 
141
+ # ═══ LoRRI Integration (Option C) ═══════════════════════════════════════��════
142
+ try:
143
+ from app.integrations.lorri import router as lorri_router
144
+ app.include_router(lorri_router, prefix="/lorri", tags=["LoRRI Integration"])
145
+ logger.info("βœ“ LoRRI integration router mounted at /lorri")
146
+ except ImportError as e:
147
+ logger.warning(f"LoRRI integration not available: {e}")
148
+
149
+
150
+ # ═══ Health & Status ══════════════════════════════════════════════════════════
151
 
152
  @app.get("/", tags=["Health"])
153
  async def root():
154
+ """Root endpoint."""
155
  return {
156
  "status": "healthy",
157
  "service": settings.app_title,
158
  "version": settings.app_version,
159
  "docs": "/docs",
160
+ "lorri_integration": "/lorri/health",
161
  }
162
 
163
 
164
  @app.get("/health", tags=["Health"])
165
  async def health_check():
166
+ """Health check with DB verification."""
167
  from app.database import check_db_health
168
  db_ok = await check_db_health()
 
169
  return {
170
+ "status": "healthy" if db_ok else "degraded",
171
  "database": "connected" if db_ok else "disconnected",
172
  "version": settings.app_version,
173
  }
 
175
 
176
  @app.get("/api/v1/health", tags=["Health"])
177
  async def api_health():
178
+ """API health for frontend connectivity."""
179
  from app.database import check_db_health
180
  db_ok = await check_db_health()
181
  return {
 
184
  "version": settings.app_version,
185
  "agents": ["ml_effort", "route_planner", "fairness_manager", "driver_liaison", "final_resolution", "explainability"],
186
  "langgraph": True,
187
+ "lorri_integration": True,
188
  }
189
 
190
 
191
+ # ═══ Static Files & Demo Pages ════════════════════════════════════════════════
192
+
193
  if FRONTEND_DIR.exists():
194
  app.mount("/static", StaticFiles(directory=str(FRONTEND_DIR)), name="static")
195
 
 
197
 
198
  @app.get("/demo/allocate", tags=["Demo"])
199
  async def demo_allocate():
200
+ """Serve the API demo page."""
201
+ return FileResponse(FRONTEND_DIR / "demo.html", media_type="text/html", headers=NO_CACHE)
 
202
 
203
  @app.get("/demo/visualization", tags=["Demo"])
204
  async def demo_visualization():
205
  """Serve the agent visualization page."""
206
+ return FileResponse(FRONTEND_DIR / "visualization.html", media_type="text/html", headers=NO_CACHE)
 
207
 
208
  @app.get("/demo/consolidation", tags=["Demo"])
209
  async def demo_consolidation():
210
+ """Serve the consolidation pipeline visualization."""
211
+ return FileResponse(FRONTEND_DIR / "consolidation.html", media_type="text/html", headers=NO_CACHE)