File size: 2,964 Bytes
37b0787
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations

import logging
import os
import time
from contextlib import asynccontextmanager

# Force offline mode globally to prevent HuggingFace hub HEAD loops
# Must be set before any sentence-transformers imports
os.environ["HF_HUB_OFFLINE"] = "1"
os.environ["TRANSFORMERS_OFFLINE"] = "1"

from fastapi import FastAPI

from src.api.middleware.logging import RequestLoggingMiddleware
from src.api.middleware.validation import InputValidationMiddleware
from src.api.routes.health import init_health, set_index_size, set_model_loaded
from src.api.routes.health import router as health_router
from src.api.routes.ingest import router as ingest_router
from src.api.routes.profiles import init_profiles
from src.api.routes.profiles import router as profiles_router
from src.api.routes.search import init_orchestrator
from src.api.routes.search import router as search_router
from src.core.config import DATA_DIR, get_settings

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


@asynccontextmanager
async def lifespan(app: FastAPI):
    _start_time = time.time()
    init_health(index_size=0)
    logger.info("Starting India Runs — Intelligent Candidate Discovery")
    indexes_dir = DATA_DIR / "indexes"
    indexes_dir.mkdir(parents=True, exist_ok=True)

    faiss_path = indexes_dir / "faiss_index.bin"
    id_map_path = indexes_dir / "faiss_id_map.json"
    bm25_path = indexes_dir / "bm25_index.pkl"

    if not faiss_path.exists():
        logger.warning(
            "No FAISS index found. Run 'python scripts/build_indexes.py --sample 50' first."
        )
        init_health(index_size=0)
        yield
        return

    from src.core.config import build_orchestrator

    orchestrator, vector_search, profiles = build_orchestrator(
        faiss_path=faiss_path,
        id_map_path=id_map_path,
        bm25_path=bm25_path,
        cross_encoder_timeout_ms=get_settings().cross_encoder_timeout_ms,
    )
    set_model_loaded("embedding", True)
    set_model_loaded("cross_encoder", True)  # already handled inside build_orchestrator

    init_orchestrator(orchestrator)
    set_index_size(vector_search.size)
    init_profiles(profiles)

    logger.info("System initialized successfully (%.1fs)", time.time() - _start_time)

    yield

    logger.info("Shutting down")


app = FastAPI(
    title="India Runs — Intelligent Candidate Discovery",
    description="Hybrid semantic search with agentic AI for candidate matching",
    version="0.1.0",
    lifespan=lifespan,
)

app.add_middleware(RequestLoggingMiddleware)
app.add_middleware(InputValidationMiddleware)
app.include_router(search_router, prefix="/api/v1")
app.include_router(profiles_router, prefix="/api/v1")
app.include_router(ingest_router, prefix="/api/v1")
app.include_router(health_router, prefix="/api/v1")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("src.main:app", host="0.0.0.0", port=8000, log_level="info")