Spaces:
No application file
No application file
Upload 33 files
Browse files- backend/Dockerfile +35 -0
- backend/__pycache__/app.cpython-312.pyc +0 -0
- backend/app.py +27 -0
- backend/config/__pycache__/city_tier.cpython-312.pyc +0 -0
- backend/config/city_tier.py +9 -0
- backend/database/__pycache__/database.cpython-312.pyc +0 -0
- backend/database/database.py +11 -0
- backend/insurance.db +0 -0
- backend/model/__pycache__/predict.cpython-312.pyc +0 -0
- backend/model/model.pkl +3 -0
- backend/model/predict.py +32 -0
- backend/models/__pycache__/user.cpython-312.pyc +0 -0
- backend/models/__pycache__/utils.cpython-312.pyc +0 -0
- backend/models/user.py +10 -0
- backend/models/utils.py +20 -0
- backend/requirements.txt +8 -0
- backend/routes/__pycache__/auth.cpython-312.pyc +0 -0
- backend/routes/__pycache__/home.cpython-312.pyc +0 -0
- backend/routes/__pycache__/predict.cpython-312.pyc +0 -0
- backend/routes/auth.py +38 -0
- backend/routes/home.py +24 -0
- backend/routes/predict.py +33 -0
- backend/schema/__pycache__/prediction_response.cpython-312.pyc +0 -0
- backend/schema/__pycache__/signloginRequest.cpython-312.pyc +0 -0
- backend/schema/__pycache__/user_input.cpython-312.pyc +0 -0
- backend/schema/prediction_response.py +19 -0
- backend/schema/signloginRequest.py +10 -0
- backend/schema/user_input.py +67 -0
- docker-compose.yml +28 -0
- frontend/Dockerfile +20 -0
- frontend/__pycache__/app.cpython-312.pyc +0 -0
- frontend/frontend.py +105 -0
- frontend/requirements.txt +7 -0
backend/Dockerfile
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# docker/Dockerfile
|
| 2 |
+
|
| 3 |
+
FROM python:3.11-slim
|
| 4 |
+
|
| 5 |
+
ENV USER=uv-example-user \
|
| 6 |
+
PYTHONDONTWRITEBYTECODE=1 \
|
| 7 |
+
PYTHONUNBUFFERED=1 \
|
| 8 |
+
UV_PROJECT_ENVIRONMENT=/usr/local
|
| 9 |
+
|
| 10 |
+
# Install required packages and dependencies
|
| 11 |
+
RUN apt-get update && apt-get install --no-install-recommends -y \
|
| 12 |
+
curl \
|
| 13 |
+
&& apt-get clean \
|
| 14 |
+
&& rm -rf /var/lib/apt/lists/* \
|
| 15 |
+
&& useradd -m -s /bin/bash $USER
|
| 16 |
+
|
| 17 |
+
# Set the working directory
|
| 18 |
+
ENV APP_DIR=/home/$USER/src
|
| 19 |
+
WORKDIR $APP_DIR
|
| 20 |
+
|
| 21 |
+
# Copy application files
|
| 22 |
+
COPY . $APP_DIR
|
| 23 |
+
|
| 24 |
+
# Install Python dependencies
|
| 25 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 26 |
+
|
| 27 |
+
# Set Python path
|
| 28 |
+
ENV PYTHONPATH=$APP_DIR
|
| 29 |
+
|
| 30 |
+
# Change ownership of the application directory
|
| 31 |
+
RUN chown -R "$USER":"$USER" $APP_DIR
|
| 32 |
+
USER $USER
|
| 33 |
+
|
| 34 |
+
# Command to run the FastAPI app
|
| 35 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
backend/__pycache__/app.cpython-312.pyc
ADDED
|
Binary file (1.06 kB). View file
|
|
|
backend/app.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from routes.home import router as home_router
|
| 3 |
+
from routes.predict import router as predict_router
|
| 4 |
+
from routes.auth import router as auth_router
|
| 5 |
+
from database.database import engine, Base
|
| 6 |
+
import uvicorn
|
| 7 |
+
|
| 8 |
+
Base.metadata.create_all(bind=engine)
|
| 9 |
+
|
| 10 |
+
app = FastAPI(title="Insurance Premium Prediction API with Auth")
|
| 11 |
+
|
| 12 |
+
# Include routers
|
| 13 |
+
app.include_router(home_router, prefix="/api", tags=["General"])
|
| 14 |
+
app.include_router(predict_router, prefix="/api", tags=["Prediction"])
|
| 15 |
+
app.include_router(auth_router, prefix="/api/auth", tags=["Authentication"])
|
| 16 |
+
|
| 17 |
+
# Add a root endpoint
|
| 18 |
+
@app.get("/")
|
| 19 |
+
def read_root():
|
| 20 |
+
return {"message": "Welcome to the Insurance Premium Prediction API"}
|
| 21 |
+
|
| 22 |
+
if __name__ == "__main__":
|
| 23 |
+
"""
|
| 24 |
+
Run the FastAPI app using uvicorn.
|
| 25 |
+
"""
|
| 26 |
+
uvicorn.run(app, host="0.0.0.0", port=8000, reload=True)
|
| 27 |
+
# To run the app, use the command: uvicorn app:app --reload
|
backend/config/__pycache__/city_tier.cpython-312.pyc
ADDED
|
Binary file (730 Bytes). View file
|
|
|
backend/config/city_tier.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
tier_1_cities = ["Mumbai", "Delhi", "Bangalore", "Chennai", "Kolkata", "Hyderabad", "Pune"]
|
| 2 |
+
tier_2_cities = [
|
| 3 |
+
"Jaipur", "Chandigarh", "Indore", "Lucknow", "Patna", "Ranchi", "Visakhapatnam", "Coimbatore",
|
| 4 |
+
"Bhopal", "Nagpur", "Vadodara", "Surat", "Rajkot", "Jodhpur", "Raipur", "Amritsar", "Varanasi",
|
| 5 |
+
"Agra", "Dehradun", "Mysore", "Jabalpur", "Guwahati", "Thiruvananthapuram", "Ludhiana", "Nashik",
|
| 6 |
+
"Allahabad", "Udaipur", "Aurangabad", "Hubli", "Belgaum", "Salem", "Vijayawada", "Tiruchirappalli",
|
| 7 |
+
"Bhavnagar", "Gwalior", "Dhanbad", "Bareilly", "Aligarh", "Gaya", "Kozhikode", "Warangal",
|
| 8 |
+
"Kolhapur", "Bilaspur", "Jalandhar", "Noida", "Guntur", "Asansol", "Siliguri"
|
| 9 |
+
]
|
backend/database/__pycache__/database.cpython-312.pyc
ADDED
|
Binary file (565 Bytes). View file
|
|
|
backend/database/database.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# database.py
|
| 2 |
+
from sqlalchemy import create_engine
|
| 3 |
+
from sqlalchemy.ext.declarative import declarative_base
|
| 4 |
+
from sqlalchemy.orm import sessionmaker
|
| 5 |
+
|
| 6 |
+
# Replace with your actual database URL
|
| 7 |
+
DATABASE_URL = "sqlite:///./insurance.db"
|
| 8 |
+
|
| 9 |
+
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
| 10 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 11 |
+
Base = declarative_base()
|
backend/insurance.db
ADDED
|
Binary file (16.4 kB). View file
|
|
|
backend/model/__pycache__/predict.cpython-312.pyc
ADDED
|
Binary file (1.4 kB). View file
|
|
|
backend/model/model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:99679b393250929802df797963a88de637f6d6f8304277d813f730bfdf424ab9
|
| 3 |
+
size 473714
|
backend/model/predict.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pickle
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
# import the ml model
|
| 5 |
+
with open('model/model.pkl', 'rb') as f:
|
| 6 |
+
model = pickle.load(f)
|
| 7 |
+
|
| 8 |
+
# MLFlow
|
| 9 |
+
MODEL_VERSION = '1.0.0'
|
| 10 |
+
|
| 11 |
+
# Get class labels from model (important for matching probabilities to class names)
|
| 12 |
+
class_labels = model.classes_.tolist()
|
| 13 |
+
|
| 14 |
+
def predict_output(user_input: dict):
|
| 15 |
+
|
| 16 |
+
df = pd.DataFrame([user_input])
|
| 17 |
+
|
| 18 |
+
# Predict the class
|
| 19 |
+
predicted_class = model.predict(df)[0]
|
| 20 |
+
|
| 21 |
+
# Get probabilities for all classes
|
| 22 |
+
probabilities = model.predict_proba(df)[0]
|
| 23 |
+
confidence = max(probabilities)
|
| 24 |
+
|
| 25 |
+
# Create mapping: {class_name: probability}
|
| 26 |
+
class_probs = dict(zip(class_labels, map(lambda p: round(p, 4), probabilities)))
|
| 27 |
+
|
| 28 |
+
return {
|
| 29 |
+
"predicted_category": predicted_class,
|
| 30 |
+
"confidence": round(confidence, 4),
|
| 31 |
+
"class_probabilities": class_probs
|
| 32 |
+
}
|
backend/models/__pycache__/user.cpython-312.pyc
ADDED
|
Binary file (646 Bytes). View file
|
|
|
backend/models/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (1.39 kB). View file
|
|
|
backend/models/user.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# models/user.py
|
| 2 |
+
from sqlalchemy import Column, Integer, String
|
| 3 |
+
from database.database import Base
|
| 4 |
+
|
| 5 |
+
class User(Base):
|
| 6 |
+
__tablename__ = "users"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 9 |
+
username = Column(String, unique=True, index=True)
|
| 10 |
+
password = Column(String)
|
backend/models/utils.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# utils.py
|
| 2 |
+
from passlib.context import CryptContext
|
| 3 |
+
from jose import jwt
|
| 4 |
+
from datetime import datetime, timedelta
|
| 5 |
+
|
| 6 |
+
SECRET_KEY = "mysecret"
|
| 7 |
+
ALGORITHM = "HS256"
|
| 8 |
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 9 |
+
|
| 10 |
+
def hash_password(password: str):
|
| 11 |
+
return pwd_context.hash(password)
|
| 12 |
+
|
| 13 |
+
def verify_password(plain: str, hashed: str):
|
| 14 |
+
return pwd_context.verify(plain, hashed)
|
| 15 |
+
|
| 16 |
+
def create_token(data: dict, expires_minutes: int = 60):
|
| 17 |
+
data = data.copy()
|
| 18 |
+
expire = datetime.utcnow() + timedelta(minutes=expires_minutes)
|
| 19 |
+
data.update({"exp": expire})
|
| 20 |
+
return jwt.encode(data, SECRET_KEY, algorithm=ALGORITHM)
|
backend/requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
sqlalchemy
|
| 4 |
+
pydantic
|
| 5 |
+
requests
|
| 6 |
+
passlib[bcrypt]
|
| 7 |
+
pandas
|
| 8 |
+
scikit-learn
|
backend/routes/__pycache__/auth.cpython-312.pyc
ADDED
|
Binary file (2.61 kB). View file
|
|
|
backend/routes/__pycache__/home.cpython-312.pyc
ADDED
|
Binary file (884 Bytes). View file
|
|
|
backend/routes/__pycache__/predict.cpython-312.pyc
ADDED
|
Binary file (1.47 kB). View file
|
|
|
backend/routes/auth.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# routes/auth.py
|
| 2 |
+
from fastapi import APIRouter, Depends, HTTPException
|
| 3 |
+
from sqlalchemy.orm import Session
|
| 4 |
+
from models.user import User
|
| 5 |
+
from database.database import SessionLocal
|
| 6 |
+
from schema.signloginRequest import SignupRequest, LoginRequest
|
| 7 |
+
from passlib.context import CryptContext
|
| 8 |
+
|
| 9 |
+
router = APIRouter()
|
| 10 |
+
|
| 11 |
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 12 |
+
|
| 13 |
+
# Dependency to get DB session
|
| 14 |
+
def get_db():
|
| 15 |
+
db = SessionLocal()
|
| 16 |
+
try:
|
| 17 |
+
yield db
|
| 18 |
+
finally:
|
| 19 |
+
db.close()
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@router.post("/signup")
|
| 23 |
+
def signup(request: SignupRequest, db: Session = Depends(get_db)):
|
| 24 |
+
if db.query(User).filter(User.username == request.username).first():
|
| 25 |
+
raise HTTPException(status_code=400, detail="Username already exists")
|
| 26 |
+
hashed_password = pwd_context.hash(request.password)
|
| 27 |
+
user = User(username=request.username, password=hashed_password)
|
| 28 |
+
db.add(user)
|
| 29 |
+
db.commit()
|
| 30 |
+
db.refresh(user)
|
| 31 |
+
return {"message": "User created successfully"}
|
| 32 |
+
|
| 33 |
+
@router.post("/login")
|
| 34 |
+
def login(request: LoginRequest, db: Session = Depends(get_db)):
|
| 35 |
+
user = db.query(User).filter(User.username == request.username).first()
|
| 36 |
+
if not user or not pwd_context.verify(request.password, user.password):
|
| 37 |
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
| 38 |
+
return {"message": "Login successful"}
|
backend/routes/home.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# routes/home.py
|
| 2 |
+
from fastapi import APIRouter
|
| 3 |
+
from model.predict import model, MODEL_VERSION
|
| 4 |
+
|
| 5 |
+
# Create router
|
| 6 |
+
router = APIRouter()
|
| 7 |
+
|
| 8 |
+
@router.get("/")
|
| 9 |
+
def home():
|
| 10 |
+
"""
|
| 11 |
+
Human-readable welcome endpoint.
|
| 12 |
+
"""
|
| 13 |
+
return {"message": "Insurance Premium Prediction API"}
|
| 14 |
+
|
| 15 |
+
@router.get("/health")
|
| 16 |
+
def health_check():
|
| 17 |
+
"""
|
| 18 |
+
Machine-readable health check endpoint.
|
| 19 |
+
"""
|
| 20 |
+
return {
|
| 21 |
+
"status": "OK",
|
| 22 |
+
"version": MODEL_VERSION,
|
| 23 |
+
"model_loaded": model is not None
|
| 24 |
+
}
|
backend/routes/predict.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# routes/predict.py
|
| 2 |
+
from fastapi import APIRouter
|
| 3 |
+
from fastapi.responses import JSONResponse
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import pickle
|
| 7 |
+
from schema.user_input import UserInput
|
| 8 |
+
from schema.prediction_response import PredictionResponse
|
| 9 |
+
from model.predict import predict_output
|
| 10 |
+
|
| 11 |
+
# Create router
|
| 12 |
+
router = APIRouter()
|
| 13 |
+
|
| 14 |
+
@router.post("/predict", response_model=PredictionResponse)
|
| 15 |
+
def predict_premium(data: UserInput):
|
| 16 |
+
"""
|
| 17 |
+
Predict insurance premium based on user input.
|
| 18 |
+
"""
|
| 19 |
+
user_input = {
|
| 20 |
+
"bmi": data.bmi,
|
| 21 |
+
"age_group": data.age_group,
|
| 22 |
+
"lifestyle_risk": data.lifestyle_risk,
|
| 23 |
+
"city_tier": data.city_tier,
|
| 24 |
+
"income_lpa": data.income_lpa,
|
| 25 |
+
"occupation": data.occupation
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
prediction = predict_output(user_input)
|
| 30 |
+
return JSONResponse(status_code=200, content={"response": prediction})
|
| 31 |
+
except Exception as e:
|
| 32 |
+
return JSONResponse(status_code=500, content={"error": str(e)})
|
| 33 |
+
|
backend/schema/__pycache__/prediction_response.cpython-312.pyc
ADDED
|
Binary file (1.01 kB). View file
|
|
|
backend/schema/__pycache__/signloginRequest.cpython-312.pyc
ADDED
|
Binary file (627 Bytes). View file
|
|
|
backend/schema/__pycache__/user_input.cpython-312.pyc
ADDED
|
Binary file (3.49 kB). View file
|
|
|
backend/schema/prediction_response.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from typing import Dict
|
| 3 |
+
|
| 4 |
+
class PredictionResponse(BaseModel):
|
| 5 |
+
predicted_category: str = Field(
|
| 6 |
+
...,
|
| 7 |
+
description="The predicted insurance premium category",
|
| 8 |
+
example="High"
|
| 9 |
+
)
|
| 10 |
+
confidence: float = Field(
|
| 11 |
+
...,
|
| 12 |
+
description="Model's confidence score for the predicted class (range: 0 to 1)",
|
| 13 |
+
example=0.8432
|
| 14 |
+
)
|
| 15 |
+
class_probabilities: Dict[str, float] = Field(
|
| 16 |
+
...,
|
| 17 |
+
description="Probability distribution across all possible classes",
|
| 18 |
+
example={"Low": 0.01, "Medium": 0.15, "High": 0.84}
|
| 19 |
+
)
|
backend/schema/signloginRequest.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
|
| 3 |
+
# Pydantic models for request validation
|
| 4 |
+
class SignupRequest(BaseModel):
|
| 5 |
+
username: str
|
| 6 |
+
password: str
|
| 7 |
+
|
| 8 |
+
class LoginRequest(BaseModel):
|
| 9 |
+
username: str
|
| 10 |
+
password: str
|
backend/schema/user_input.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field, computed_field, field_validator
|
| 2 |
+
from typing import Literal, Annotated
|
| 3 |
+
from config.city_tier import tier_1_cities, tier_2_cities
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# pydantic model to validate incoming data
|
| 7 |
+
class UserInput(BaseModel):
|
| 8 |
+
|
| 9 |
+
age: Annotated[int, Field(..., gt=0, lt=120, description='Age of the user')]
|
| 10 |
+
weight: Annotated[float, Field(..., gt=0, description='Weight of the user')]
|
| 11 |
+
height: Annotated[float, Field(..., gt=0, lt=2.5, description='Height of the user')]
|
| 12 |
+
income_lpa: Annotated[float, Field(..., gt=0, description='Annual salary of the user in lpa')]
|
| 13 |
+
smoker: Annotated[bool, Field(..., description='Is user a smoker')]
|
| 14 |
+
city: Annotated[str, Field(..., description='The city that the user belongs to')]
|
| 15 |
+
occupation: Annotated[Literal['retired', 'freelancer', 'student', 'government_job',
|
| 16 |
+
'business_owner', 'unemployed', 'private_job'], Field(..., description='Occupation of the user')]
|
| 17 |
+
|
| 18 |
+
@field_validator('city')
|
| 19 |
+
@classmethod
|
| 20 |
+
def normalize_city(cls, v: str) -> str:
|
| 21 |
+
v = v.strip().title()
|
| 22 |
+
return v
|
| 23 |
+
|
| 24 |
+
@computed_field
|
| 25 |
+
@property
|
| 26 |
+
def bmi(self) -> float:
|
| 27 |
+
return self.weight/(self.height**2)
|
| 28 |
+
|
| 29 |
+
@computed_field
|
| 30 |
+
@property
|
| 31 |
+
def lifestyle_risk(self) -> str:
|
| 32 |
+
if self.smoker and self.bmi > 30:
|
| 33 |
+
return "high"
|
| 34 |
+
elif self.smoker or self.bmi > 27:
|
| 35 |
+
return "medium"
|
| 36 |
+
else:
|
| 37 |
+
return "low"
|
| 38 |
+
|
| 39 |
+
@computed_field
|
| 40 |
+
@property
|
| 41 |
+
def age_group(self) -> str:
|
| 42 |
+
if self.age < 25:
|
| 43 |
+
return "young"
|
| 44 |
+
elif self.age < 45:
|
| 45 |
+
return "adult"
|
| 46 |
+
elif self.age < 60:
|
| 47 |
+
return "middle_aged"
|
| 48 |
+
return "senior"
|
| 49 |
+
|
| 50 |
+
@computed_field
|
| 51 |
+
@property
|
| 52 |
+
def city_tier(self) -> int:
|
| 53 |
+
if self.city in tier_1_cities:
|
| 54 |
+
return 1
|
| 55 |
+
elif self.city in tier_2_cities:
|
| 56 |
+
return 2
|
| 57 |
+
else:
|
| 58 |
+
return 3
|
| 59 |
+
# Pydantic model for input validation
|
| 60 |
+
class PredictionInput(BaseModel):
|
| 61 |
+
age: int
|
| 62 |
+
weight: float
|
| 63 |
+
height: float
|
| 64 |
+
income_lpa: float
|
| 65 |
+
smoker: bool
|
| 66 |
+
city: str
|
| 67 |
+
occupation: str
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: "3.9"
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
backend:
|
| 5 |
+
build:
|
| 6 |
+
context: ./backend
|
| 7 |
+
dockerfile: Dockerfile
|
| 8 |
+
container_name: insurance-backend
|
| 9 |
+
ports:
|
| 10 |
+
- "8000:8000"
|
| 11 |
+
environment:
|
| 12 |
+
- PYTHONUNBUFFERED=1
|
| 13 |
+
volumes:
|
| 14 |
+
- ./backend:/home/uv-example-user/src
|
| 15 |
+
command: ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
| 16 |
+
|
| 17 |
+
frontend:
|
| 18 |
+
build:
|
| 19 |
+
context: ./frontend
|
| 20 |
+
dockerfile: Dockerfile
|
| 21 |
+
container_name: insurance-frontend
|
| 22 |
+
ports:
|
| 23 |
+
- "8501:8501"
|
| 24 |
+
environment:
|
| 25 |
+
- STREAMLIT_SERVER_PORT=8501
|
| 26 |
+
volumes:
|
| 27 |
+
- ./frontend:/app
|
| 28 |
+
command: ["streamlit", "run", "frontend.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
frontend/Dockerfile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# filepath: d:\Insurance Premium Predictor\frontend\Dockerfile
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
# Install required packages
|
| 5 |
+
RUN apt-get update && apt-get install --no-install-recommends -y \
|
| 6 |
+
curl \
|
| 7 |
+
&& apt-get clean \
|
| 8 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 9 |
+
|
| 10 |
+
# Set the working directory
|
| 11 |
+
WORKDIR /app
|
| 12 |
+
|
| 13 |
+
# Copy application files
|
| 14 |
+
COPY . /app
|
| 15 |
+
|
| 16 |
+
# Upgrade pip and install Python dependencies
|
| 17 |
+
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
|
| 18 |
+
|
| 19 |
+
# Command to run the Streamlit app
|
| 20 |
+
CMD ["streamlit", "run", "frontend.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
frontend/__pycache__/app.cpython-312.pyc
ADDED
|
Binary file (7.34 kB). View file
|
|
|
frontend/frontend.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import requests
|
| 3 |
+
|
| 4 |
+
# Update API_URL to use the backend service name
|
| 5 |
+
API_URL = "http://127.0.0.1:8000"
|
| 6 |
+
st.set_page_config(page_title="Insurance Premium Predictor", layout="centered")
|
| 7 |
+
|
| 8 |
+
# Resize title and subtitle
|
| 9 |
+
st.markdown("<h1 style='text-align: center; font-size: 36px;'>Insurance Premium Category Predictor</h1>", unsafe_allow_html=True)
|
| 10 |
+
st.markdown("<h3 style='text-align: center; font-size: 20px;'>Enter your details below to predict your insurance premium category:</h3>", unsafe_allow_html=True)
|
| 11 |
+
|
| 12 |
+
# Session state to track login status
|
| 13 |
+
if "logged_in" not in st.session_state:
|
| 14 |
+
st.session_state.logged_in = False
|
| 15 |
+
|
| 16 |
+
# Sidebar for Authentication
|
| 17 |
+
with st.sidebar:
|
| 18 |
+
st.markdown("## Authentication")
|
| 19 |
+
auth_mode = st.selectbox("Choose an option", ["Login", "Signup"], help="Select whether to log in or sign up.")
|
| 20 |
+
|
| 21 |
+
if auth_mode == "Login":
|
| 22 |
+
st.markdown("### Login")
|
| 23 |
+
login_username = st.text_input("Username", key="login_username", help="Enter your username.")
|
| 24 |
+
login_password = st.text_input("Password", type="password", key="login_password", help="Enter your password.")
|
| 25 |
+
if st.button("Login"):
|
| 26 |
+
login_data = {"username": login_username, "password": login_password}
|
| 27 |
+
try:
|
| 28 |
+
with st.spinner("Logging in..."):
|
| 29 |
+
response = requests.post(API_URL + "/api/auth/login", json=login_data, timeout=10)
|
| 30 |
+
response.raise_for_status()
|
| 31 |
+
st.success("Login successful!")
|
| 32 |
+
st.session_state.logged_in = True # Set login status to True
|
| 33 |
+
except requests.exceptions.HTTPError as e:
|
| 34 |
+
st.error(f"Login failed: {response.json().get('detail', str(e))}")
|
| 35 |
+
except requests.exceptions.RequestException as e:
|
| 36 |
+
st.error(f"An error occurred: {e}")
|
| 37 |
+
|
| 38 |
+
elif auth_mode == "Signup":
|
| 39 |
+
st.markdown("### Signup")
|
| 40 |
+
signup_username = st.text_input("Signup Username", key="signup_username", help="Enter a unique username.")
|
| 41 |
+
signup_password = st.text_input("Signup Password", type="password", key="signup_password", help="Enter a secure password.")
|
| 42 |
+
if st.button("Signup"):
|
| 43 |
+
signup_data = {"username": signup_username, "password": signup_password}
|
| 44 |
+
try:
|
| 45 |
+
with st.spinner("Creating account..."):
|
| 46 |
+
response = requests.post(API_URL + "/api/auth/signup", json=signup_data, timeout=10)
|
| 47 |
+
response.raise_for_status()
|
| 48 |
+
st.success("Account created successfully! You can now log in.")
|
| 49 |
+
except requests.exceptions.HTTPError as e:
|
| 50 |
+
st.error(f"Signup failed: {response.json().get('detail', str(e))}")
|
| 51 |
+
except requests.exceptions.RequestException as e:
|
| 52 |
+
st.error(f"An error occurred: {e}")
|
| 53 |
+
|
| 54 |
+
# Show Prediction Section only if logged in
|
| 55 |
+
if st.session_state.logged_in:
|
| 56 |
+
# Display all input fields in a single column
|
| 57 |
+
age = st.number_input("Age", min_value=1, max_value=119, value=30, help="Enter your age in years.")
|
| 58 |
+
weight = st.number_input("Weight (kg)", min_value=1.0, value=65.0, help="Enter your weight in kilograms.")
|
| 59 |
+
height = st.number_input("Height (m)", min_value=0.5, max_value=2.5, value=1.7, help="Enter your height in meters.")
|
| 60 |
+
income_lpa = st.number_input("Annual Income (LPA)", min_value=0.1, value=10.0, help="Enter your annual income in lakhs per annum.")
|
| 61 |
+
smoker = st.selectbox("Are you a smoker?", options=["Yes", "No"], help="Select 'Yes' if you are a smoker, otherwise 'No'.")
|
| 62 |
+
city = st.text_input("City", value="Mumbai", help="Enter the city you reside in.")
|
| 63 |
+
occupation = st.selectbox(
|
| 64 |
+
"Occupation",
|
| 65 |
+
['retired', 'freelancer', 'student', 'government_job', 'business_owner', 'unemployed', 'private_job'],
|
| 66 |
+
help="Select your occupation from the dropdown."
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
# Submit button for Prediction
|
| 70 |
+
if st.button("Predict Premium Category"):
|
| 71 |
+
input_data = {
|
| 72 |
+
"age": age,
|
| 73 |
+
"weight": weight,
|
| 74 |
+
"height": height,
|
| 75 |
+
"income_lpa": income_lpa,
|
| 76 |
+
"smoker": smoker.lower() == "yes",
|
| 77 |
+
"city": city,
|
| 78 |
+
"occupation": occupation
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
try:
|
| 82 |
+
with st.spinner("Predicting..."):
|
| 83 |
+
# Increased timeout to 30 seconds
|
| 84 |
+
response = requests.post(API_URL + "/api/predict", json=input_data, timeout=30)
|
| 85 |
+
response.raise_for_status() # Raise HTTPError for bad responses (4xx and 5xx)
|
| 86 |
+
|
| 87 |
+
result = response.json()
|
| 88 |
+
if "response" in result:
|
| 89 |
+
prediction = result["response"]
|
| 90 |
+
st.success(f"Predicted Insurance Premium Category: **{prediction['predicted_category']}**")
|
| 91 |
+
st.markdown(f"**Confidence:** {prediction['confidence']:.2f}")
|
| 92 |
+
st.markdown("### Class Probabilities:")
|
| 93 |
+
st.json(prediction["class_probabilities"])
|
| 94 |
+
else:
|
| 95 |
+
st.error("Unexpected response format from the API.")
|
| 96 |
+
st.json(result)
|
| 97 |
+
|
| 98 |
+
except requests.exceptions.ConnectionError:
|
| 99 |
+
st.error("❌ Could not connect to the FastAPI server. Please ensure it is running.")
|
| 100 |
+
except requests.exceptions.Timeout:
|
| 101 |
+
st.error("⏳ The request timed out. Please try again later.")
|
| 102 |
+
except requests.exceptions.RequestException as e:
|
| 103 |
+
st.error(f"⚠️ An error occurred: {e}")
|
| 104 |
+
else:
|
| 105 |
+
st.warning("Please log in to access the prediction feature.")
|
frontend/requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit==1.45.1
|
| 2 |
+
pandas
|
| 3 |
+
requests
|
| 4 |
+
altair==5.5.0
|
| 5 |
+
numpy
|
| 6 |
+
python-dateutil
|
| 7 |
+
pytz
|