This commit is contained in:
User Name 2025-06-07 23:27:56 +02:00
commit 95826b438c
13 changed files with 682 additions and 0 deletions

6
.dockerignore Normal file
View File

@ -0,0 +1,6 @@
__pycache__
*.pyc
*.pyo
*.pyd
*.git
*myenv

2
.env Normal file
View File

@ -0,0 +1,2 @@
MODEL_PATH=linear_regression_model.pkl
SCALER_PATH=scaler.pkl

38
.github/workflows/deploy.yml vendored Normal file
View 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
View 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

Binary file not shown.

87
main.py Normal file
View 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
View File

@ -0,0 +1,9 @@
fastapi
uvicorn
joblib
numpy
pandas
scikit-learn
python-dotenv
pytest
httpx

BIN
scaler.pkl Normal file

Binary file not shown.

16
static/index.html Normal file
View 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
View 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>&copy; 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
View 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
View 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

File diff suppressed because one or more lines are too long