File size: 10,411 Bytes
fc4db52
56fed02
79ef7e1
15213e6
79ef7e1
 
 
 
 
 
 
15213e6
 
 
fc4db52
79ef7e1
 
 
 
 
fc4db52
46cc8b9
 
 
 
 
 
79ef7e1
56fed02
8c9362b
56fed02
 
8c9362b
56fed02
 
 
 
 
 
 
 
 
8c9362b
56fed02
8c9362b
56fed02
79ef7e1
56fed02
79ef7e1
fc4db52
15213e6
 
505f1d7
fc4db52
79ef7e1
 
46cc8b9
e2099f4
56fed02
 
 
 
e2099f4
4119538
fc4db52
79ef7e1
fc4db52
79ef7e1
 
15213e6
56fed02
79ef7e1
56fed02
79ef7e1
4119538
79ef7e1
fc4db52
15213e6
 
56fed02
15213e6
56fed02
79ef7e1
56fed02
4119538
79ef7e1
fc4db52
15213e6
 
56fed02
15213e6
56fed02
79ef7e1
56fed02
4119538
15213e6
fc4db52
15213e6
56fed02
15213e6
56fed02
4119538
15213e6
fc4db52
15213e6
56fed02
15213e6
56fed02
4119538
15213e6
fc4db52
15213e6
56fed02
15213e6
56fed02
4119538
e2099f4
56fed02
e2099f4
4119538
79ef7e1
4119538
fc4db52
e2099f4
56fed02
e2099f4
4119538
79ef7e1
15213e6
 
 
56fed02
15213e6
 
4119538
15213e6
79ef7e1
56fed02
4119538
15213e6
 
56fed02
4119538
56fed02
79ef7e1
56fed02
79ef7e1
15213e6
fc4db52
79ef7e1
 
56fed02
 
79ef7e1
 
 
fc4db52
79ef7e1
 
 
 
 
 
 
e2099f4
8c9362b
79ef7e1
 
15213e6
8c9362b
79ef7e1
 
 
 
 
 
15213e6
79ef7e1
 
 
 
 
fc4db52
79ef7e1
 
fc4db52
79ef7e1
 
 
 
 
 
 
 
46cc8b9
 
79ef7e1
 
 
 
15213e6
79ef7e1
a7d89f2
fc4db52
79ef7e1
 
 
 
 
15213e6
fc4db52
56fed02
fc4db52
56fed02
15213e6
56fed02
79ef7e1
4119538
56fed02
8c9362b
56fed02
8c9362b
56fed02
8c9362b
 
56fed02
8c9362b
 
 
56fed02
8c9362b
 
 
56fed02
 
 
 
4119538
 
6038791
4119538
 
 
6038791
15213e6
56fed02
 
 
15213e6
 
fc4db52
79ef7e1
 
 
46cc8b9
15213e6
 
 
 
 
 
 
 
4119538
15213e6
 
 
 
 
 
 
4119538
15213e6
 
 
 
 
4119538
15213e6
 
 
56fed02
 
15213e6
56fed02
 
 
 
15213e6
 
 
 
 
 
 
 
fc4db52
15213e6
 
 
 
 
79ef7e1
 
 
46cc8b9
79ef7e1
 
 
 
 
56fed02
 
15213e6
56fed02
 
 
 
79ef7e1
 
15213e6
79ef7e1
 
46cc8b9
79ef7e1
 
15213e6
fc4db52
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# app/main.py
# Lojiz Platform + Aida AI - Graph-Based Architecture (v1 Primary)

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from contextlib import asynccontextmanager
import logging
import os

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# CORE IMPORTS
try:
    from app.config import settings
    from app.database import connect_db, disconnect_db, ensure_indexes as ensure_auth_indexes
    from app.routes import auth
except ImportError as e:
    logger.error(f"Core import error: {e}")
    raise

try:
    from app.core.exceptions import AuthException
except ImportError:
    AuthException = Exception

# ============================================================
# AI IMPORTS - GRAPH-BASED ARCHITECTURE
# ============================================================
try:
    from app.ai.routes.chat import router as ai_chat_router
    from app.ai.config import (
        validate_ai_startup,
        check_redis_health,
        check_qdrant_health,
        redis_client,
        qdrant_client,
    )
    from app.ai.memory.redis_context_memory import get_memory_manager
    from app.ml.models.ml_listing_extractor import get_ml_extractor
    logger.info("βœ… Graph-based AI architecture loaded")
except ImportError as e:
    logger.error(f"AI import error: {e}")
    raise

from app.models.listing import ensure_listing_indexes

# ENVIRONMENT
environment = os.getenv("ENVIRONMENT", "development")
is_production = environment == "production"

# LIFESPAN
@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifespan - startup and shutdown"""
    logger.info("=" * 70)
    logger.info("πŸš€ Starting Lojiz Platform + Aida AI")
    logger.info("   Architecture: Graph-Based (State Machine + Validation)")
    logger.info("   Primary Endpoint: /ai/v1 (Graph-Based)")
    logger.info("   Fallback Endpoint: /ai/v2 (Legacy)")
    logger.info("=" * 70)

    # STARTUP
    try:
        logger.info("Connecting to MongoDB...")
        await connect_db()
        await ensure_auth_indexes()
        await ensure_listing_indexes()
        logger.info("βœ… MongoDB connected and indexed")
    except Exception as e:
        logger.critical(f"❌ MongoDB connection failed - aborting startup: {e}")
        raise

    try:
        logger.info("Connecting to Redis...")
        if redis_client:
            await redis_client.ping()
            logger.info("βœ… Redis connected")
        else:
            logger.warning("⚠️ Redis not available (optional)")
    except Exception as e:
        logger.warning(f"⚠️ Redis connection failed (continuing without): {e}")

    try:
        logger.info("Connecting to Qdrant...")
        if qdrant_client:
            await qdrant_client.get_collections()
            logger.info("βœ… Qdrant connected")
        else:
            logger.warning("⚠️ Qdrant not available (optional)")
    except Exception as e:
        logger.warning(f"⚠️ Qdrant connection failed (continuing without): {e}")

    try:
        logger.info("Validating AI components...")
        ai_checks = await validate_ai_startup()
        logger.info("βœ… AI components validated")
    except Exception as e:
        logger.warning(f"⚠️ AI validation warning: {e}")

    try:
        logger.info("Initializing ML Extractor...")
        ml = get_ml_extractor()
        logger.info("βœ… ML Extractor ready")
    except Exception as e:
        logger.warning(f"⚠️ ML Extractor initialization warning: {e}")

    try:
        logger.info("Initializing Memory Manager...")
        manager = get_memory_manager()
        logger.info("βœ… Memory Manager ready")
    except Exception as e:
        logger.warning(f"⚠️ Memory Manager initialization warning: {e}")

    logger.info("=" * 70)
    logger.info("βœ… APPLICATION READY - Graph-Based Architecture Active!")
    logger.info("=" * 70)

    yield

    # SHUTDOWN
    logger.info("=" * 70)
    logger.info("πŸ›‘ Shutting down Lojiz Platform + Aida AI")
    logger.info("=" * 70)

    try:
        try:
            ml = get_ml_extractor()
            ml.currency_mgr.clear_cache()
            logger.info("βœ… ML caches cleared")
        except:
            pass

        from app.database import disconnect_db
        await disconnect_db()
        logger.info("βœ… MongoDB disconnected")

        if redis_client:
            await redis_client.close()
            logger.info("βœ… Redis closed")

        logger.info("βœ… Shutdown complete")
    except Exception as e:
        logger.warning(f"⚠️ Shutdown warning: {e}")


# FASTAPI SETUP
app = FastAPI(
    title="Lojiz Platform + Aida AI",
    description="Real-estate platform with conversational AI assistant (Graph-Based Architecture)",
    version="2.0.0",
    lifespan=lifespan,
)

# CORS
cors_origins = [
    "https://lojiz.onrender.com",
    "https://lojiz.com",
    "https://www.lojiz.com",
] if is_production else [
    "http://localhost",
    "http://localhost:3000",
    "http://localhost:5173",
    "http://localhost:8080",  # Test UI
    "http://127.0.0.1",
    "http://127.0.0.1:3000",
    "http://127.0.0.1:5173",
    "http://127.0.0.1:8080",  # Test UI
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
    expose_headers=["*"],
    max_age=600,
)

# EXCEPTION HANDLERS
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    logger.error(f"Validation error: {exc}")
    errors = []
    for error in exc.errors():
        field = ".".join(str(loc) for loc in error["loc"][1:])
        errors.append({"field": field, "message": error["msg"]})
    return JSONResponse(
        status_code=400,
        content={
            "success": False,
            "message": "Validation error. Please check your input.",
            "error_code": "VALIDATION_ERROR",
            "errors": errors,
        },
    )


@app.exception_handler(AuthException)
async def auth_exception_handler(request: Request, exc: AuthException): # type: ignore
    logger.warning(f"Auth error [{exc.error_code}]: {exc.message}")
    response = {"success": False, "message": exc.message, "error_code": exc.error_code}
    if exc.data:
        response["data"] = exc.data
    return JSONResponse(status_code=exc.status_code, content=response)


# ROUTERS
logger.info("=" * 70)
logger.info("Registering routers...")
logger.info("=" * 70)

# Authentication
app.include_router(auth.router, prefix="/api/auth", tags=["Authentication"])

# ============================================================
# AI CHAT ROUTES (SINGLE GRAPH-BASED ENDPOINT)
# ============================================================
# Consolidated to single endpoint: /ai/ask (Graph-Based)

try:
    from app.ai.routes.chat import router as ai_chat_router
    app.include_router(
        ai_chat_router,
        prefix="/ai",
        tags=["AIDA AI Chat (Graph-Based)"]
    )
    logger.info("βœ… AIDA AI Chat registered at /ai/ask (Graph-Based)")
except ImportError as e:
    logger.error(f"❌ AI Chat import error: {e}")

# ============================================================
# LISTING ROUTERS
# ============================================================
from app.routes.listing import router as listing_router
from app.routes.user_public import router as user_public_router
from app.routes.websocket_listings import router as ws_router

app.include_router(listing_router, prefix="/api/listings", tags=["Listings"])
app.include_router(user_public_router, prefix="/api/users", tags=["Users"])
app.include_router(ws_router, tags=["WebSocket"])

logger.info("=" * 70)
logger.info("βœ… All routers registered successfully")
logger.info("=" * 70)


# ENDPOINTS

@app.get("/health", tags=["Health"])
async def health_check():
    """Health check endpoint"""
    try:
        redis_ok = False
        if redis_client:
            try:
                await redis_client.ping()
                redis_ok = True
            except:
                redis_ok = False

        qdrant_ok = False
        if qdrant_client:
            try:
                await qdrant_client.get_collections()
                qdrant_ok = True
            except:
                qdrant_ok = False

        try:
            ml = get_ml_extractor()
            ml_ok = ml is not None
        except:
            ml_ok = False

        return {
            "status": "healthy",
            "service": "Lojiz Platform + Aida AI",
            "version": "2.0.0",
            "architecture": "Graph-Based (State Machine + Validation)",
            "environment": environment,
            "ai_endpoints": {
                "primary": "/ai/v1 (Graph-Based)",
                "fallback": "/ai/v2 (Legacy)",
            },
            "components": {
                "mongodb": "connected",
                "redis": "connected" if redis_ok else "disconnected",
                "qdrant": "connected" if qdrant_ok else "disconnected",
                "ml": "ready" if ml_ok else "not ready",
            }
        }
    except Exception as e:
        logger.error(f"Health check failed: {e}")
        return {
            "status": "unhealthy",
            "error": str(e),
        }


@app.get("/", tags=["Root"])
async def root():
    """Root endpoint - API information"""
    return {
        "message": "Welcome to Lojiz Platform + Aida AI",
        "docs": "/docs",
        "health": "/health",
        "environment": environment,
        "version": "2.0.0",
        "architecture": "Graph-Based (State Machine + Validation)",
        "description": "Real-estate platform with conversational AI assistant (Aida)",
        "ai_chat": {
            "primary": "/ai/v1/ask (Graph-Based - 95% reliable)",
            "fallback": "/ai/v2/ask (Legacy - for emergency use)",
        },
    }


@app.options("/{full_path:path}", include_in_schema=False)
async def options_handler(full_path: str):
    """Handle CORS preflight requests"""
    return JSONResponse(status_code=200, content={})


# RUN
# To run this application:
# Development: uvicorn app.main:app --reload
# Production: gunicorn -w 4 -k uvicorn.workers.UvicornWorker app.main:app
# HF Spaces: python app.py