SURESHBEEKHANI commited on
Commit
4c4571b
·
verified ·
1 Parent(s): bd9cfc7

Upload 12 files

Browse files
.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ /.venv/
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.13-slim
2
+
3
+ # Set the working directory in the container
4
+ WORKDIR /app
5
+
6
+ # Copy the requirements file into the container
7
+ COPY requirements.txt /app/requirements.txt
8
+
9
+ # Install dependencies
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ # Copy the application code into the container
13
+ COPY . /app
14
+
15
+ # Expose ports for FastAPI and Streamlit
16
+ EXPOSE 8000 8501
17
+
18
+ # Command to run both FastAPI and Streamlit
19
+ CMD ["sh", "-c", "uvicorn app:app --host 0.0.0.0 --port 8000 & streamlit run frontend.py --server.port 8501 --server.address 0.0.0.0"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Suresh Beekhani
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
File without changes
app.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.responses import JSONResponse
3
+ from schema.user_input import UserInput
4
+ from schema.prediction_response import PredictionResponse
5
+ from model.predict import predict_output, model, MODEL_VERSION
6
+ import uvicorn
7
+
8
+ app = FastAPI()
9
+
10
+ # human readable
11
+ @app.get('/')
12
+ def home():
13
+ return {'message':'Insurance Premium Prediction API'}
14
+
15
+ # machine readable
16
+ @app.get('/health')
17
+ def health_check():
18
+ return {
19
+ 'status': 'OK',
20
+ 'version': MODEL_VERSION,
21
+ 'model_loaded': model is not None
22
+ }
23
+
24
+ @app.post('/predict', response_model=PredictionResponse)
25
+ def predict_premium(data: UserInput):
26
+
27
+ user_input = {
28
+ 'bmi': data.bmi,
29
+ 'age_group': data.age_group,
30
+ 'lifestyle_risk': data.lifestyle_risk,
31
+ 'city_tier': data.city_tier,
32
+ 'income_lpa': data.income_lpa,
33
+ 'occupation': data.occupation
34
+ }
35
+
36
+ try:
37
+
38
+ prediction = predict_output(user_input)
39
+
40
+ return JSONResponse(status_code=200, content={'response': prediction})
41
+
42
+ except Exception as e:
43
+
44
+ return JSONResponse(status_code=500, content=str(e))
45
+
46
+ if __name__ == "__main__":
47
+ """
48
+ Run the FastAPI app using uvicorn.
49
+ """
50
+ uvicorn.run(app, host="127.0.0.1", port=8000, reload=True)
51
+ # To run the app, use the command: uvicorn app:app --reload
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
+ ]
frontend.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+
4
+ API_URL = "http://127.0.0.1:8000"
5
+
6
+ st.title("Insurance Premium Category Predictor")
7
+ st.markdown("Enter your details below:")
8
+
9
+ # Input fields
10
+ age = st.number_input("Age", min_value=1, max_value=119, value=30)
11
+ weight = st.number_input("Weight (kg)", min_value=1.0, value=65.0)
12
+ height = st.number_input("Height (m)", min_value=0.5, max_value=2.5, value=1.7)
13
+ income_lpa = st.number_input("Annual Income (LPA)", min_value=0.1, value=10.0)
14
+ smoker = st.selectbox("Are you a smoker?", options=[True, False])
15
+ city = st.text_input("City", value="Mumbai")
16
+ occupation = st.selectbox(
17
+ "Occupation",
18
+ ['retired', 'freelancer', 'student', 'government_job', 'business_owner', 'unemployed', 'private_job']
19
+ )
20
+
21
+ if st.button("Predict Premium Category"):
22
+ input_data = {
23
+ "age": age,
24
+ "weight": weight,
25
+ "height": height,
26
+ "income_lpa": income_lpa,
27
+ "smoker": smoker,
28
+ "city": city,
29
+ "occupation": occupation
30
+ }
31
+
32
+ try:
33
+ response = requests.post(API_URL + "/predict", json=input_data, timeout=10)
34
+ response.raise_for_status() # Raise HTTPError for bad responses (4xx and 5xx)
35
+
36
+ result = response.json()
37
+ if "response" in result:
38
+ prediction = result["response"]
39
+ st.success(f"Predicted Insurance Premium Category: **{prediction['predicted_category']}**")
40
+ st.markdown(f"**Confidence:** {prediction['confidence']:.2f}")
41
+ st.markdown("### Class Probabilities:")
42
+ st.json(prediction["class_probabilities"])
43
+ else:
44
+ st.error("Unexpected response format from the API.")
45
+ st.json(result)
46
+
47
+ except requests.exceptions.ConnectionError:
48
+ st.error("❌ Could not connect to the FastAPI server. Please ensure it is running.")
49
+ except requests.exceptions.Timeout:
50
+ st.error("⏳ The request timed out. Please try again later.")
51
+ except requests.exceptions.RequestException as e:
52
+ st.error(f"⚠️ An error occurred: {e}")
model/model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:99679b393250929802df797963a88de637f6d6f8304277d813f730bfdf424ab9
3
+ size 473714
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
+ }
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ pydantic
4
+ requests
5
+ streamlit
6
+ pandas
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
+ )
schema/user_input.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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