Files
Jonathan Pressnell 1a8ec37bed feat: Complete Week 2 - Document Processing Pipeline
- Implement multi-format document support (PDF, XLSX, CSV, PPTX, TXT, Images)
- Add S3-compatible storage service with tenant isolation
- Create document organization service with hierarchical folders and tagging
- Implement advanced document processing with table/chart extraction
- Add batch upload capabilities (up to 50 files)
- Create comprehensive document validation and security scanning
- Implement automatic metadata extraction and categorization
- Add document version control system
- Update DEVELOPMENT_PLAN.md to mark Week 2 as completed
- Add WEEK2_COMPLETION_SUMMARY.md with detailed implementation notes
- All tests passing (6/6) - 100% success rate
2025-08-08 15:47:43 -04:00

218 lines
6.4 KiB
Python

"""
Main FastAPI application for the Virtual Board Member AI System.
"""
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.responses import JSONResponse
import structlog
from app.core.config import settings
from app.core.database import engine, Base
from app.middleware.tenant import TenantMiddleware
from app.api.v1.api import api_router
from app.services.vector_service import vector_service
from app.core.cache import cache_service
from app.core.auth import auth_service
# Configure structured logging
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.processors.JSONRenderer()
],
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger,
cache_logger_on_first_use=True,
)
logger = structlog.get_logger()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan manager."""
# Startup
logger.info("Starting Virtual Board Member AI System")
# Initialize database
try:
Base.metadata.create_all(bind=engine)
logger.info("Database tables created/verified")
except Exception as e:
logger.error(f"Database initialization failed: {e}")
raise
# Initialize services
try:
# Initialize vector service
if await vector_service.health_check():
logger.info("Vector service initialized successfully")
else:
logger.warning("Vector service health check failed")
# Initialize cache service
if cache_service.redis_client:
logger.info("Cache service initialized successfully")
else:
logger.warning("Cache service initialization failed")
# Initialize auth service
if auth_service.redis_client:
logger.info("Auth service initialized successfully")
else:
logger.warning("Auth service initialization failed")
except Exception as e:
logger.error(f"Service initialization failed: {e}")
raise
logger.info("Virtual Board Member AI System started successfully")
yield
# Shutdown
logger.info("Shutting down Virtual Board Member AI System")
# Cleanup services
try:
if vector_service.client:
vector_service.client.close()
logger.info("Vector service connection closed")
if cache_service.redis_client:
await cache_service.redis_client.close()
logger.info("Cache service connection closed")
if auth_service.redis_client:
await auth_service.redis_client.close()
logger.info("Auth service connection closed")
except Exception as e:
logger.error(f"Service cleanup failed: {e}")
logger.info("Virtual Board Member AI System shutdown complete")
# Create FastAPI application
app = FastAPI(
title=settings.PROJECT_NAME,
description="Enterprise-grade AI assistant for board members and executives",
version=settings.VERSION,
openapi_url=f"{settings.API_V1_STR}/openapi.json",
docs_url="/docs",
redoc_url="/redoc",
lifespan=lifespan
)
# Add middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_HOSTS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=settings.ALLOWED_HOSTS
)
# Add tenant middleware
app.add_middleware(TenantMiddleware)
# Global exception handler
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""Global exception handler."""
logger.error(
"Unhandled exception",
path=request.url.path,
method=request.method,
error=str(exc),
exc_info=True
)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={"detail": "Internal server error"}
)
# Health check endpoint
@app.get("/health")
async def health_check():
"""Health check endpoint."""
health_status = {
"status": "healthy",
"version": settings.VERSION,
"services": {}
}
# Check vector service
try:
vector_healthy = await vector_service.health_check()
health_status["services"]["vector"] = "healthy" if vector_healthy else "unhealthy"
except Exception as e:
logger.error(f"Vector service health check failed: {e}")
health_status["services"]["vector"] = "unhealthy"
# Check cache service
try:
cache_healthy = cache_service.redis_client is not None
health_status["services"]["cache"] = "healthy" if cache_healthy else "unhealthy"
except Exception as e:
logger.error(f"Cache service health check failed: {e}")
health_status["services"]["cache"] = "unhealthy"
# Check auth service
try:
auth_healthy = auth_service.redis_client is not None
health_status["services"]["auth"] = "healthy" if auth_healthy else "unhealthy"
except Exception as e:
logger.error(f"Auth service health check failed: {e}")
health_status["services"]["auth"] = "unhealthy"
# Overall health status
all_healthy = all(
status == "healthy"
for status in health_status["services"].values()
)
if not all_healthy:
health_status["status"] = "degraded"
return health_status
# Include API router
app.include_router(api_router, prefix=settings.API_V1_STR)
# Root endpoint
@app.get("/")
async def root():
"""Root endpoint."""
return {
"message": "Virtual Board Member AI System",
"version": settings.VERSION,
"docs": "/docs",
"health": "/health"
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app.main:app",
host=settings.HOST,
port=settings.PORT,
reload=settings.DEBUG,
log_level="info"
)