Ismail-707 commited on
Commit
fe70d38
·
1 Parent(s): 1291dfb

add docker file app/ and req file

Browse files
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 2 stages build
2
+ FROM python:3.12-slim AS python-base
3
+ WORKDIR /app
4
+ # We need to add the path of our virtual env to our $PATH environment variable :
5
+ ENV PATH=".venv/bin:$PATH"
6
+
7
+ # Our builder image to install the libraries
8
+ FROM python-base AS python-builder
9
+ RUN apt-get update && \
10
+ apt-get upgrade -y && \
11
+ apt-get install -y gcc \
12
+ && apt-get clean
13
+
14
+ COPY ./requirements.txt ./
15
+ RUN python -m venv .venv
16
+ # Install requirements
17
+ RUN pip install -r requirements.txt
18
+
19
+
20
+ # Our final image
21
+ FROM python-base
22
+ COPY --from=python-builder /app/.venv ./.venv
23
+ COPY ./app /app
24
+
25
+ EXPOSE 5000
26
+
27
+ ENTRYPOINT gunicorn --bind 0.0.0.0:5000 main:app
app/.DS_Store ADDED
Binary file (6.15 kB). View file
 
app/__init__.py ADDED
File without changes
app/main.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pickle
3
+ import requests
4
+ from dotenv import load_dotenv
5
+ from flask import Flask, render_template, request
6
+ from pydantic import BaseModel, Field, PositiveFloat
7
+
8
+ app = Flask(__name__)
9
+ MODEL = pickle.load(open("model.sav", "rb"))
10
+
11
+ PRICE_BASE = 10**5
12
+
13
+ load_dotenv(override=False)
14
+
15
+ API_HOST = os.environ.get("API_HOST", "localhost")
16
+
17
+
18
+ class FormQuery(BaseModel):
19
+ med_inc: PositiveFloat = Field(..., validation_alias="MedInc")
20
+ house_age: PositiveFloat = Field(..., validation_alias="HouseAge")
21
+ ave_rooms: PositiveFloat = Field(..., validation_alias="AveRooms")
22
+ ave_bedrms: PositiveFloat = Field(..., validation_alias="AveBedrms")
23
+ population: PositiveFloat = Field(..., validation_alias="Population")
24
+ ave_occup: PositiveFloat = Field(..., validation_alias="AveOccup")
25
+ latitude: float = Field(..., validation_alias="Latitude")
26
+ longitude: float = Field(..., validation_alias="Longitude")
27
+
28
+
29
+ @app.route("/", methods=["GET"])
30
+ def california_index():
31
+ return render_template("index.html")
32
+
33
+
34
+ @app.route("/predict/", methods=["POST"])
35
+ def local_model_result():
36
+ form_query = FormQuery(**request.form.to_dict(flat=True))
37
+
38
+ reg = MODEL.predict(
39
+ [
40
+ [
41
+ form_query.med_inc,
42
+ form_query.house_age,
43
+ form_query.ave_rooms,
44
+ form_query.ave_bedrms,
45
+ form_query.population,
46
+ form_query.ave_occup,
47
+ form_query.latitude,
48
+ form_query.longitude,
49
+ ]
50
+ ]
51
+ )[0]
52
+ return render_template("prediction.html", price=reg * PRICE_BASE)
53
+
54
+
55
+ @app.route("/predict_from_api/", methods=["POST"])
56
+ def api_result():
57
+ model_list = requests.get(f"http://{API_HOST}:8000/model/list/").json()
58
+ if len(model_list) == 0:
59
+ raise Exception("No model could be retrieved from the model registry")
60
+
61
+ best_model = sorted(model_list, key=lambda d: d["rse"])[0]
62
+ app.logger.debug(f"Best model retrieved : {best_model}")
63
+
64
+ api_response = requests.post(
65
+ f"http://{API_HOST}:8000/model/predict/",
66
+ json={
67
+ **{"train_id": best_model["train_id"]},
68
+ **FormQuery(**request.form.to_dict(flat=True)).model_dump(),
69
+ },
70
+ )
71
+
72
+ response = api_response.json()
73
+ app.logger.debug(response)
74
+
75
+ return render_template("prediction.html", price=response["reg"] * PRICE_BASE)
76
+
77
+
78
+ if __name__ == "__main__":
79
+ app.debug = True
80
+ app.run(host="0.0.0.0", port=5000, debug=True)
app/model.sav ADDED
Binary file (482 Bytes). View file
 
app/static/css/bootstrap.min.css ADDED
The diff for this file is too large to render. See raw diff
 
app/static/css/jumbotron-narrow.css ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Space out content a bit */
2
+ body {
3
+ padding-top: 20px;
4
+ padding-bottom: 20px;
5
+ }
6
+
7
+ a, a:hover, a:visited, a:link, a:active{
8
+ text-decoration: none;
9
+ }
10
+
11
+ /* Everything but the jumbotron gets side spacing for mobile first views */
12
+ .header,
13
+ .marketing,
14
+ .footer {
15
+ padding-right: 15px;
16
+ padding-left: 15px;
17
+ }
18
+
19
+ /* Custom page header */
20
+ .header {
21
+ padding-bottom: 20px;
22
+ border-bottom: 1px solid #e5e5e5;
23
+ }
24
+ /* Make the masthead heading the same height as the navigation */
25
+ .header h3 {
26
+ margin-top: 0;
27
+ margin-bottom: 0;
28
+ line-height: 40px;
29
+ }
30
+
31
+ /* Custom page footer */
32
+ .footer {
33
+ padding-top: 19px;
34
+ color: #777;
35
+ border-top: 1px solid #e5e5e5;
36
+ }
37
+
38
+ /* Customize container */
39
+ @media (min-width: 768px) {
40
+ .container {
41
+ max-width: 730px;
42
+ }
43
+ }
44
+ .container-narrow > hr {
45
+ margin: 30px 0;
46
+ }
47
+
48
+ /* Main marketing message and sign up button */
49
+ .jumbotron {
50
+ text-align: center;
51
+ border-bottom: 1px solid #e5e5e5;
52
+ }
53
+ .jumbotron .btn {
54
+ padding: 14px 24px;
55
+ font-size: 21px;
56
+ }
57
+
58
+ /* Supporting marketing content */
59
+ .marketing {
60
+ margin: 40px 0;
61
+ }
62
+ .marketing p + h4 {
63
+ margin-top: 28px;
64
+ }
65
+
66
+ /* Responsive: Portrait tablets and up */
67
+ @media screen and (min-width: 768px) {
68
+ /* Remove the padding we set earlier */
69
+ .header,
70
+ .marketing,
71
+ .footer {
72
+ padding-right: 0;
73
+ padding-left: 0;
74
+ }
75
+ /* Space out the masthead */
76
+ .header {
77
+ margin-bottom: 30px;
78
+ }
79
+ /* Remove the bottom border on the jumbotron for visual effect */
80
+ .jumbotron {
81
+ border-bottom: 0;
82
+ }
83
+ }
84
+
85
+ #selector {
86
+ width: 600px;
87
+ height: 200px;
88
+ }
app/static/img/setosa.jpg ADDED
app/static/img/versicolor.jpg ADDED
app/static/img/virginica.jpg ADDED
app/templates/index.html ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
8
+ <meta name="description" content="">
9
+ <meta name="author" content="">
10
+ <link rel="icon" href="../../favicon.ico">
11
+
12
+ <title>VIVADATA |California ML</title>
13
+
14
+ <!-- Bootstrap core CSS -->
15
+ <link href="../static/css/bootstrap.min.css" rel="stylesheet">
16
+
17
+ <!-- Custom styles for this template -->
18
+ <link href="../static/css/jumbotron-narrow.css" rel="stylesheet">
19
+
20
+ </head>
21
+
22
+ <body>
23
+
24
+ <div class="container">
25
+ <div class="header clearfix">
26
+ <h3 class="text-muted">VIVADATA - Flask Demo</h3>
27
+ </div>
28
+
29
+ <div class="jumbotron">
30
+ <h1>Predicting of California Housing 🏘️</h1>
31
+ <p class="lead">Fill the needed data to predict the median value!</p>
32
+ <!-- <p><a class="btn btn-lg btn-success" href="home" role="button">Get started</a></p> -->
33
+ </div class="form-row">
34
+ <form action="" id="prediction-form" method="post">
35
+ <div class="form-row">
36
+ <div class="form-group col-md-6">
37
+ <label for="MedInc">Median income in block</label>
38
+ <input type="number" step="0.01" class="form-control" id="MedInc" name="MedInc" placeholder="Enter Value">
39
+ </div>
40
+ <div class="form-group col-md-6">
41
+ <label for="HouseAge">Median house age in block</label>
42
+ <input type="number" step="0.01" class="form-control" id="HouseAge" name="HouseAge" placeholder="Enter Value">
43
+ </div>
44
+ </div>
45
+ <div class="form-row">
46
+ <div class="form-group col-md-6">
47
+ <label for="AveRooms">Average number of rooms</label>
48
+ <input type="number" step="0.01" class="form-control" id="AveRooms" name="AveRooms" placeholder="Enter Value">
49
+ </div>
50
+ <div class="form-group col-md-6">
51
+ <label for="AveBedrms">Average number of bedrooms</label>
52
+ <input type="number" step="0.01" class="form-control" id="AveBedrms" name="AveBedrms" placeholder="Enter Value">
53
+ </div>
54
+ </div>
55
+ <div class="form-row">
56
+ <div class="form-group col-md-6">
57
+ <label for="Population">Block population</label>
58
+ <input type="number" step="0.01" class="form-control" id="Population" name="Population" placeholder="Enter Value">
59
+ </div>
60
+ <div class="form-group col-md-6">
61
+ <label for="AveOccup">Average house occupancy</label>
62
+ <input type="number" step="0.01" class="form-control" id="AveOccup" name="AveOccup" placeholder="Enter Value">
63
+ </div>
64
+ </div>
65
+ <div class="form-row">
66
+ <div class="form-group col-md-6">
67
+ <label for="Latitude">House block latitude</label>
68
+ <input type="number" step="0.01" class="form-control" id="Latitude" name="Latitude" placeholder="Enter Value">
69
+ </div>
70
+ <div class="form-group col-md-6">
71
+ <label for="Longitude">House block longitude</label>
72
+ <input type="number" step="0.01" class="form-control" id="Longitude" name="Longitude" placeholder="Enter Value">
73
+ </div>
74
+ </div>
75
+ <button type="submit" class="btn btn-primary" onclick="setAction('/predict/')">Predict via local model</button>
76
+ <button type="submit" class="btn btn-primary" onclick="setAction('/predict_from_api/')">Predict via API</button>
77
+ </form>
78
+ <br>
79
+ <footer class="footer">
80
+ <p>&copy; Vivadata 2019</p>
81
+ </footer>
82
+
83
+ </div> <!-- /container -->
84
+ </body>
85
+ </html>
86
+
87
+ <script>
88
+ function setAction(action) {
89
+ document.getElementById('prediction-form').action = action;
90
+ }
91
+ </script>
app/templates/prediction.html ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
8
+ <meta name="description" content="">
9
+ <meta name="author" content="">
10
+ <link rel="icon" href="../../favicon.ico">
11
+
12
+ <title>VIVADATA | Prediction</title>
13
+
14
+ <!-- Bootstrap core CSS -->
15
+ <link href="../static/css/bootstrap.min.css" rel="stylesheet">
16
+
17
+ <!-- Custom styles for this template -->
18
+ <link href="../static/css/jumbotron-narrow.css" rel="stylesheet">
19
+
20
+ </head>
21
+
22
+ <body>
23
+
24
+ <div class="container">
25
+ <div class="header clearfix">
26
+ <h3 class="text-muted">VIVADATA - Flask Demo</h3>
27
+ </div>
28
+
29
+ <div class="jumbotron">
30
+ <h1>Median house value: {{price}} 💵</h1>
31
+ </div>
32
+
33
+ <br>
34
+ <div class="text-center">
35
+ <a href="/" class="btn btn-primary">Back</a>
36
+ </div>
37
+
38
+ <br>
39
+ <footer class="footer">
40
+ <p>&copy; Vivadata 2019</p>
41
+ </footer>
42
+
43
+ </div> <!-- /container -->
44
+ </body>
45
+ </html>
46
+
requirements.txt CHANGED
@@ -30,4 +30,4 @@ threadpoolctl==3.2.0
30
  typing_extensions==4.8.0
31
  urllib3==2.0.7
32
  uvicorn==0.23.2
33
- Werkzeug==3.0.1
 
30
  typing_extensions==4.8.0
31
  urllib3==2.0.7
32
  uvicorn==0.23.2
33
+ Werkzeug==3.0.1