init
This commit is contained in:
commit
95826b438c
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.git
|
||||
*myenv
|
||||
2
.env
Normal file
2
.env
Normal file
@ -0,0 +1,2 @@
|
||||
MODEL_PATH=linear_regression_model.pkl
|
||||
SCALER_PATH=scaler.pkl
|
||||
38
.github/workflows/deploy.yml
vendored
Normal file
38
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
name: Deploy to Heroku
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9 # Match your app's Python version
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Test Application
|
||||
run: |
|
||||
pytest # Assuming you have tests configured
|
||||
|
||||
- name: Deploy to Heroku
|
||||
env:
|
||||
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
|
||||
HEROKU_APP_NAME: ${{ secrets.HEROKU_APP_NAME }}
|
||||
run: |
|
||||
echo "Deploying to Heroku..."
|
||||
heroku container:login
|
||||
heroku container:push web --app $HEROKU_APP_NAME
|
||||
heroku container:release web --app $HEROKU_APP_NAME
|
||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@ -0,0 +1,20 @@
|
||||
# Use an offical python runtime as a parent image
|
||||
FROM python:3.9-slim
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the requirements file into the container
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy the application code into the container
|
||||
COPY . .
|
||||
|
||||
# Expose the port the app runs on
|
||||
EXPOSE 8000
|
||||
|
||||
# Ensure the application runs on Heroku's dynamic port
|
||||
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port $PORT"]
|
||||
BIN
linear_regression_model.pkl
Normal file
BIN
linear_regression_model.pkl
Normal file
Binary file not shown.
87
main.py
Normal file
87
main.py
Normal file
@ -0,0 +1,87 @@
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import joblib
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import logging
|
||||
from fastapi import HTTPException
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
import uvicorn
|
||||
|
||||
# Load the saved model and scaler
|
||||
model = None
|
||||
scaler = None
|
||||
|
||||
# Load environment variables from .env file in the same directory
|
||||
load_dotenv()
|
||||
|
||||
# Initialize FastAPI app
|
||||
app = FastAPI()
|
||||
|
||||
# Serve the static files from the static folder
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def load_model():
|
||||
global model, scaler
|
||||
try:
|
||||
# Use environment variables to load model and scaler paths
|
||||
model_path = os.getenv("MODEL_PATH", "linear_regression_model.pkl")
|
||||
scaler_path = os.getenv("SCALER_PATH", "scaler.pkl")
|
||||
|
||||
model = joblib.load(model_path)
|
||||
scaler = joblib.load(scaler_path)
|
||||
logging.info("Model and scaler have been loaded successfully.")
|
||||
except Exception as e:
|
||||
logging.error(f"Error loading model or scaler: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error loading model")
|
||||
|
||||
# Define a request model for the input
|
||||
class PredictionRequest(BaseModel):
|
||||
hours_studied: float
|
||||
|
||||
# Home endpoint
|
||||
@app.get("/")
|
||||
def home():
|
||||
return FileResponse("static/index.html")
|
||||
|
||||
# Prediction page endpoint
|
||||
@app.get("/predict")
|
||||
async def predict_page():
|
||||
return FileResponse("static/predict.html")
|
||||
|
||||
# Prediction endpoint
|
||||
@app.post("/predict")
|
||||
def predict(request: PredictionRequest):
|
||||
if model is None or scaler is None:
|
||||
logging.error("Model is not loaded.")
|
||||
raise HTTPException(status_code=503, detail="Model not loaded, please try again later")
|
||||
|
||||
# Input validation: Ensure hours_studied is a positive number
|
||||
if request.hours_studied <= 0:
|
||||
logging.warning("Received invalid input: Hours studied should be positive.")
|
||||
raise HTTPException(status_code=400, detail="Hours studied should be a positive number")
|
||||
|
||||
hours = request.hours_studied # Extract the hours studied from the request
|
||||
data = pd.DataFrame([[hours]], columns=['Hours_Studied']) # Prepare the data for prediction
|
||||
scaled_data = scaler.transform(data) # Scale the input data
|
||||
|
||||
try:
|
||||
prediction = model.predict(scaled_data) # Make prediction using the model
|
||||
logging.info(f"Prediction for {hours} hours: {prediction[0]}")
|
||||
except Exception as e:
|
||||
logging.error(f"Error during prediction: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error during prediction")
|
||||
|
||||
return {"predicted_test_score": prediction[0]} # Return the predicted test score
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
port = int(os.getenv("PORT", 8000)) # Use Heroku's PORT
|
||||
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||
9
requirements.txt
Normal file
9
requirements.txt
Normal file
@ -0,0 +1,9 @@
|
||||
fastapi
|
||||
uvicorn
|
||||
joblib
|
||||
numpy
|
||||
pandas
|
||||
scikit-learn
|
||||
python-dotenv
|
||||
pytest
|
||||
httpx
|
||||
BIN
scaler.pkl
Normal file
BIN
scaler.pkl
Normal file
Binary file not shown.
16
static/index.html
Normal file
16
static/index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Model Prediction Home</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" id="home">
|
||||
<h1>Welcome to Meta Brain's Prediction Model</h1>
|
||||
<p>Find out your predicted test score based on your hours of study.</p>
|
||||
<a href="/predict">Go to Prediction Page</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
44
static/predict.html
Normal file
44
static/predict.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Predict Test Score</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container" id="predict">
|
||||
<h2>Predict Your Test Score</h2>
|
||||
<form action="/predict" method="post" id="predict-form">
|
||||
<label for="hours_studied">Hours Studied:</label>
|
||||
<input type="number" id="hours_studied" name="hours_studied" required placeholder="Enter hours studied" min="0">
|
||||
<button type="submit">Get Prediction</button>
|
||||
</form>
|
||||
<div id="result" class="result"></div>
|
||||
<footer>
|
||||
<p>© 2024 Meta Brains. All rights reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById("predict-form").addEventListener("submit", function(event) {
|
||||
event.preventDefault();
|
||||
const hoursStudied = document.getElementById("hours_studied").value;
|
||||
fetch("/predict", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ hours_studied: parseFloat(hoursStudied) })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById("result").innerText = `Predicted Test Score: ${data.predicted_test_score}`;
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById("result").innerText = "Error in prediction. Please try again later.";
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
97
static/style.css
Normal file
97
static/style.css
Normal file
@ -0,0 +1,97 @@
|
||||
/* style.css */
|
||||
|
||||
body {
|
||||
font-family: 'Arial', sans-serif;
|
||||
background-color: #f4f7fa;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 80%;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
color: #555;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
margin-bottom: 20px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 20px;
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#predicted_score {
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
}
|
||||
31
test_api.py
Normal file
31
test_api.py
Normal file
@ -0,0 +1,31 @@
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from main import app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
"""Create a TestClient instance for making requests."""
|
||||
with TestClient(app) as client:
|
||||
yield client
|
||||
|
||||
# Test home route
|
||||
def test_home(client):
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
assert "text/html" in response.headers["content-type"]
|
||||
assert "Welcome to Meta Brain's Prediction Model" in response.text # Check if the static index page content is correct
|
||||
|
||||
|
||||
# Test predict page route
|
||||
def test_predict_page(client):
|
||||
response = client.get("/predict")
|
||||
assert response.status_code == 200
|
||||
assert "text/html" in response.headers["content-type"]
|
||||
assert "Predict Test Score" in response.text # Check if predict page content is present
|
||||
|
||||
def test_predict_invalid_input(client):
|
||||
response = client.post("/predict", json={"hours_studied": -2.0})
|
||||
assert response.status_code == 400
|
||||
assert "Hours studied should be a positive number" in response.json()["detail"]
|
||||
|
||||
332
train_model.ipynb
Normal file
332
train_model.ipynb
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user