Merge in dev to Master #1

Merged
armistace merged 5 commits from dev into master 2024-09-16 13:27:12 +10:00
14 changed files with 134 additions and 126 deletions

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# BEER SG data storer
This container will store SG data for beer runs in Mongo and then use that data to calculate Alchohol Content of each beer
It's pretty loose and requires the Data Entry operator to know whats happening
Luck for me... That's me!

View File

@ -1,4 +1,5 @@
FROM git.aridgwayweb.com/armistace/beer_base_image AS flask #FROM git.aridgwayweb.com/armistace/beer_base_image AS flask
FROM beer_base_image AS flask
COPY requirements.txt . COPY requirements.txt .
@ -8,6 +9,6 @@ ENV FLASK_ENV production
ENV FLASK_DEBUG 1 ENV FLASK_DEBUG 1
ENTRYPOINT ["flask", "--app", "/pool_data/src/flask/beer_data", "run", "--host=0.0.0.0"] ENTRYPOINT ["flask", "--app", "/beer_data/src/flask/beer_data", "run", "--host=0.0.0.0"]
#ENTRYPOINT ["python", "/pool_data/src/flask/pool_data.py"] #ENTRYPOINT ["python", "/pool_data/src/flask/pool_data.py"]

View File

@ -7,3 +7,5 @@ Flask-WTF
bootstrap-flask bootstrap-flask
waitress waitress
bokeh bokeh
pandas
duckdb

View File

@ -20,7 +20,7 @@ def main(username, password):
"password" : f"{password}" "password" : f"{password}"
} }
if user_collection.user_exists(new_record["username"]): if user_collection.user_exists(new_record["username"]):
user_collect.update_user(user_collection.existing_record["_id"], new_record["password"]) user_collection.update_user(user_collection.existing_record["_id"], new_record["password"])
else: else:
user_collection.add_user(new_record["username"], new_record["password"]) user_collection.add_user(new_record["username"], new_record["password"])

View File

@ -1,11 +1,12 @@
from bokeh.core.enums import SpatialUnitsType from bokeh.core.enums import SpatialUnitsType
import mongo.build_db as pool_database import mongo.build_db as beer_database
import mongo.query_db as pool_database_query import mongo.query_db as beer_database_query
from table import table_builder
from flask import Flask, render_template, request, jsonify, redirect, session from flask import Flask, render_template, request, jsonify, redirect, session
from flask_wtf import FlaskForm, CSRFProtect from flask_wtf import FlaskForm, CSRFProtect
from flask_bootstrap import Bootstrap5 from flask_bootstrap import Bootstrap5
from wtforms import StringField, SubmitField, DateField, IntegerField, PasswordField, DecimalField, RadioField, TextAreaField from wtforms import StringField, SubmitField, DateField, IntegerField, PasswordField, DecimalField, RadioField, TextAreaField, BooleanField
from wtforms.validators import DataRequired, Length, Optional from wtforms.validators import DataRequired, Length, Optional
from waitress import serve from waitress import serve
from bokeh.models.layouts import HBox from bokeh.models.layouts import HBox
@ -14,6 +15,7 @@ from charts import BeerCharts
from bokeh.io import output_file, show from bokeh.io import output_file, show
from bokeh.layouts import row from bokeh.layouts import row
app = Flask(__name__) app = Flask(__name__)
app.secret_key = 'testsecret' #this value will change app.secret_key = 'testsecret' #this value will change
@ -26,7 +28,7 @@ output_file("/beer_data/src/flask/static/data_plot.html")
def create_graphs(): def create_graphs():
chart = BeerCharts.BeerCharts() chart = BeerCharts.BeerCharts()
sg = chart.line_chart("SG", "sg", 30) sg = chart.line_chart("SG", "sg", 30)
show(row(column(sg))) show(column(sg))
class userForm(FlaskForm): class userForm(FlaskForm):
@ -39,6 +41,7 @@ class dataForm(FlaskForm):
beer_run_type = TextAreaField("Beer Type") beer_run_type = TextAreaField("Beer Type")
Date = DateField("Date:") Date = DateField("Date:")
sg = IntegerField("SG Reading") sg = IntegerField("SG Reading")
final_run = BooleanField("Final Reading?")
comment = TextAreaField("Any Comments?", validators=[Optional()]) comment = TextAreaField("Any Comments?", validators=[Optional()])
submit = SubmitField("Write it, Write it REAAAAAAL GOOOD") submit = SubmitField("Write it, Write it REAAAAAAL GOOOD")
@ -48,7 +51,7 @@ def index():
if form.validate_on_submit(): if form.validate_on_submit():
username = form.username.data username = form.username.data
password = form.password.data password = form.password.data
db = pool_database_query.pool_query() db = beer_database_query.pool_query()
if db.user_check(username, password): if db.user_check(username, password):
session['logged_in'] = True session['logged_in'] = True
return redirect("/updater") return redirect("/updater")
@ -61,18 +64,24 @@ def index():
def updater(): def updater():
if 'logged_in' not in session: if 'logged_in' not in session:
return redirect("/") return redirect("/")
create_graphs() predicted_alc_table = table_builder.TableBuilder().table_build()
query_db = pool_database_query.pool_query() tr_replace_string = '<tr align="center" style="border-bottom:1pt solid black;">'
beer_html = predicted_alc_table.to_html(col_space='75px', index=False,
justify='center', border=3).replace('<tr>', tr_replace_string)
query_db = beer_database_query.pool_query()
query = query_db.get_top(10, "sg") query = query_db.get_top(10, "sg")
form = dataForm() form = dataForm()
if form.validate_on_submit(): if form.validate_on_submit():
database = pool_database.pool_data() database = beer_database.beer_data()
new_record = { new_record = {
"date": f'{form.Date.data}', "date": f'{form.Date.data}',
"beer_run_id": f'{form.beer_run_id.data}', "beer_run_id": f'{form.beer_run_id.data}',
"beer_type": f'{form.beer_run_type.data}', "beer_type": f'{form.beer_run_type.data}',
"sg" : f'{form.sg.data}', "sg" : f'{form.sg.data}',
"final_reading" : F'{form.final_run.data}',
"comment": f'{form.comment.data}' "comment": f'{form.comment.data}'
} }
if database.record_exists(new_record["date"], new_record["beer_run_id"]): if database.record_exists(new_record["date"], new_record["beer_run_id"]):
@ -82,34 +91,40 @@ def updater():
if database.existing_record[field] != new_record[field]: if database.existing_record[field] != new_record[field]:
database.update_re_record(database.existing_record["_id"], field, new_record[field]) database.update_re_record(database.existing_record["_id"], field, new_record[field])
else: else:
database.create_re_record(new_record["beer_run_id"], new_record["beer_type"], new_record["sg"], if new_record["final_reading"] == "True":
new_record["date"], new_record["comment"]) final_run_value = True
return render_template("updater.html", list=query, form=form, success=True, updater_name = new_record["test_user"])
else:
return render_template("updater.html", list=query, form=form, sucess=False)
@app.route("/update_db", methods=["POST"])
def pool_data_update():
database = pool_database.pool_data()
new_record = request.json
if database.record_exists(new_record["date"], new_record["test_user"]):
for field in database.existing_record:
for new_field in new_record:
if field == new_field:
if database.existing_record[field] != new_record[field]:
database.update_re_record(database.existing_record["_id"], field, new_record[field])
else: else:
final_run_value = False
database.create_re_record(new_record["beer_run_id"], new_record["beer_type"], new_record["sg"], database.create_re_record(new_record["beer_run_id"], new_record["beer_type"], new_record["sg"],
new_record["date"], new_record["comment"]) new_record["date"], final_run_value, new_record["comment"])
return render_template("updater.html", beer_data = beer_html
, list=query, form=form, success=True, updater_name = "Saucy Beer Maker")
else:
return render_template("updater.html", beer_data = beer_html
, list=query, form=form, sucess=False)
# @app.route("/update_db", methods=["POST"])
# def beer_data_update():
# database = beer_database.beer_data()
# new_record = request.json
# if database.record_exists(new_record["date"], new_record["test_user"]):
# for field in database.existing_record:
# for new_field in new_record:
# if field == new_field:
# if database.existing_record[field] != new_record[field]:
# database.update_re_record(database.existing_record["_id"], field, new_record[field])
# else:
# database.create_re_record(new_record["beer_run_id"], new_record["beer_type"], new_record["sg"],
# new_record["date"], new_record["comment"])
@app.route("/pool_top/<int:return_number>/<string:field>") # @app.route("/pool_top/<int:return_number>/<string:field>")
def user_detail(id): # def user_detail(id):
query_db = pool_database_query.pool_query() # query_db = beer_database_query.pool_query()
query = query_db.get_top(return_number, field) # query = query_db.get_top(return_number, field)
return jsonify([row.to_json() for row in query]) # return jsonify([row.to_json() for row in query])
if __name__ == '__main__': if __name__ == '__main__':
#app.run(host='0.0.0.0') #app.run(host='0.0.0.0')

View File

@ -1,61 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bokeh Plot</title>
<style>
html, body {
box-sizing: border-box;
display: flow-root;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-3.5.0.min.js"></script>
<script type="text/javascript">
Bokeh.set_log_level("info");
</script>
</head>
<body>
<div id="bb5c0e1d-dc95-4d96-9cbc-a29ed2eade5c" data-root-id="p1001" style="display: contents;"></div>
<script type="application/json" id="af8187ed-c6d3-40c3-84a3-bbe58799b466">
{"00bec54e-f52e-4b7f-b2b6-8e6e4eb73c82":{"version":"3.5.0","title":"Bokeh Application","roots":[{"type":"object","name":"Figure","id":"p1001","attributes":{"height":250,"x_range":{"type":"object","name":"FactorRange","id":"p1011","attributes":{"factors":["2024-03-27","2024-03-25","2024-03-15","2024-03-13"]}},"y_range":{"type":"object","name":"DataRange1d","id":"p1003","attributes":{"start":6}},"x_scale":{"type":"object","name":"CategoricalScale","id":"p1012"},"y_scale":{"type":"object","name":"LinearScale","id":"p1013"},"title":{"type":"object","name":"Title","id":"p1004","attributes":{"text":"Pool data"}},"renderers":[{"type":"object","name":"GlyphRenderer","id":"p1030","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1024","attributes":{"selected":{"type":"object","name":"Selection","id":"p1025","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1026"},"data":{"type":"map","entries":[["x",["2024-03-27","2024-03-25","2024-03-15","2024-03-13"]],["y",[7.25,7.5,7.6,7.0]]]}}},"view":{"type":"object","name":"CDSView","id":"p1031","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1032"}}},"glyph":{"type":"object","name":"Line","id":"p1027","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1f77b4"}},"nonselection_glyph":{"type":"object","name":"Line","id":"p1028","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1f77b4","line_alpha":0.1}},"muted_glyph":{"type":"object","name":"Line","id":"p1029","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1f77b4","line_alpha":0.2}}}}],"toolbar":{"type":"object","name":"Toolbar","id":"p1010"},"toolbar_location":null,"left":[{"type":"object","name":"LinearAxis","id":"p1019","attributes":{"ticker":{"type":"object","name":"BasicTicker","id":"p1020","attributes":{"mantissas":[1,2,5]}},"formatter":{"type":"object","name":"BasicTickFormatter","id":"p1021"},"major_label_policy":{"type":"object","name":"AllLabels","id":"p1022"}}}],"below":[{"type":"object","name":"CategoricalAxis","id":"p1014","attributes":{"ticker":{"type":"object","name":"CategoricalTicker","id":"p1015"},"formatter":{"type":"object","name":"CategoricalTickFormatter","id":"p1016"},"major_label_policy":{"type":"object","name":"AllLabels","id":"p1017"}}}],"center":[{"type":"object","name":"Grid","id":"p1018","attributes":{"axis":{"id":"p1014"},"grid_line_color":null}},{"type":"object","name":"Grid","id":"p1023","attributes":{"dimension":1,"axis":{"id":"p1019"}}}]}}]}}
</script>
<script type="text/javascript">
(function() {
const fn = function() {
Bokeh.safely(function() {
(function(root) {
function embed_document(root) {
const docs_json = document.getElementById('af8187ed-c6d3-40c3-84a3-bbe58799b466').textContent;
const render_items = [{"docid":"00bec54e-f52e-4b7f-b2b6-8e6e4eb73c82","roots":{"p1001":"bb5c0e1d-dc95-4d96-9cbc-a29ed2eade5c"},"root_ids":["p1001"]}];
root.Bokeh.embed.embed_items(docs_json, render_items);
}
if (root.Bokeh !== undefined) {
embed_document(root);
} else {
let attempts = 0;
const timer = setInterval(function(root) {
if (root.Bokeh !== undefined) {
clearInterval(timer);
embed_document(root);
} else {
attempts++;
if (attempts > 100) {
clearInterval(timer);
console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing");
}
}
}, 10, root)
}
})(window);
});
};
if (document.readyState != "loading") fn();
else document.addEventListener("DOMContentLoaded", fn);
})();
</script>
</body>
</html>

View File

@ -1,17 +0,0 @@
from bokeh.models.layouts import HBox
from bokeh.plotting import column
from charts import PoolCharts
from bokeh.io import output_file, show
from bokeh.layouts import row
output_file("static/data_plot.html")
chart = PoolCharts.PoolCharts()
ph = chart.line_chart("Pool PH", "ph", 50)
total_chlorine = chart.line_chart("Pool Total Chlorine", "total_chlorine", 50)
free_chlorine = chart.line_chart("Pool Free Chlorine", "free_chlorine", 50)
show(column(ph, total_chlorine, free_chlorine))

View File

@ -9,9 +9,12 @@ class BeerCharts():
def __init__(self): def __init__(self):
self.db = db_conn() self.db = db_conn()
def bar_chart(self, items, data):
return 0
def line_chart(self, title, field, limit): def line_chart(self, title, field, limit):
data = self.db.pool_db data = self.db.beer_db
data = data.find({}).sort("date", -1).limit(limit) data = data.find({}).sort("date", -1).limit(limit)
dates = [] dates = []
data_list = [] data_list = []

View File

@ -15,14 +15,14 @@ class beer_data:
#we can get self.db.real_db etc #we can get self.db.real_db etc
self.db = db_conn() self.db = db_conn()
def record_exists(self, date, test_user): def record_exists(self, date, beer_run_id):
""" """
This function will accept an address This function will accept an address
if it find that address in the database it will return True if it find that address in the database it will return True
and set set the existing_record variable of the class to the and set set the existing_record variable of the class to the
queried record queried record
""" """
query = { "date" : f"{date}", "test_user": f"{test_user}"} query = { "date" : f"{date}", "beer_run_id": f"{beer_run_id}"}
record = self.db.beer_db.find_one(query) record = self.db.beer_db.find_one(query)
if record: if record:
self.existing_record = record self.existing_record = record
@ -31,7 +31,7 @@ class beer_data:
return False return False
def create_re_record(self, beer_run_id, beer_type, sg, date, comment=""): def create_re_record(self, beer_run_id, beer_type, sg, date, final_reading=False, comment=""):
""" """
create_re_record creates a whole new record create_re_record creates a whole new record
takes the required 7 inputs takes the required 7 inputs
@ -51,6 +51,7 @@ class beer_data:
"beer_type": f"{beer_type}", "beer_type": f"{beer_type}",
"sg": f"{sg}", "sg": f"{sg}",
"date": f"{date}", "date": f"{date}",
"final_reading": f"{final_reading}",
"comment": f"{comment}" "comment": f"{comment}"
} }

View File

@ -8,7 +8,7 @@ class db_conn:
self.db_host = os.getenv('MONGO_HOST') self.db_host = os.getenv('MONGO_HOST')
self.client = self.get_client() self.client = self.get_client()
self.db = self.client['beer_db'] self.db = self.client['beer_db']
self.pool_db = self.db['beer_data'] self.beer_db = self.db['beer_data']
self.users = self.db['users'] self.users = self.db['users']
self.inspections = self.db['inspections_db'] self.inspections = self.db['inspections_db']

View File

View File

@ -0,0 +1,54 @@
import sys
from mongo.get_conn import db_conn
import pandas as pd
import duckdb
class TableBuilder():
def __init__(self) -> None:
self.db = db_conn()
def table_build(self, limit=10) -> pd.DataFrame:
data = self.db.beer_db
data = data.find({}).sort("date", -1)
df_dict = {}
df_dict["beer_run_id"]=[]
df_dict["sg"] = []
df_dict["date"] = []
df_dict["final_reading"] = []
for record in data:
df_dict["beer_run_id"].append(record["beer_run_id"])
df_dict["sg"].append(record["sg"])
df_dict["date"].append(record["date"])
df_dict["final_reading"].append(record["final_reading"])
df = pd.DataFrame(data=df_dict)
sql = f"""
SELECT x.beer_run_id as beer_run_id,
max(sg) as max,
min(sg) as min,
y.date as final_reading_date
FROM df x
JOIN
( SELECT DISTINCT beer_run_id, date
FROM df
WHERE final_reading = 'True'
) y ON x.beer_run_id = y.beer_run_id
GROUP BY x.beer_run_id, y.date
ORDER BY x.beer_run_id desc
LIMIT {limit}
"""
df_sum = duckdb.sql(sql).df()
sql = f"""
SELECT x.beer_run_id as "Beer Run",
x.max as "Max",
x.min as "Min",
ROUND(((CAST (max AS INTEGER) - CAST(min AS INTEGER)) / 7.36) + 0.5, 2) AS "Alcohol Prediction",
cast(final_reading_date as DATE) + INTERVAL 14 DAY as "Ready Date"
FROM df_sum x
"""
df_calc = duckdb.sql(sql).df()
return df_calc

View File

@ -46,17 +46,15 @@
</div> </div>
</div> </div>
</td> --> </td> -->
<td> <!--<iframe style="width: 100vw; height: 40vh;" src="static/data_plot.html" frameborder="0" allowfullscreen>
<div class="container"> <!--<iframe style="width: 100vw;height: 80vh;position: relative;" src="static/data_plot.html" frameborder="0" allowfullscreen>-->
<div class= "col-xs-12 col-sm-12 col-md-12">
<iframe style="width: 100vw;height: 80vh;position: relative;" src="static/data_plot.html" frameborder="0" allowfullscreen>
</iframe>
</div>
</div>
</td>
</tr> </tr>
</table> </table>
</div> </div>
<div class="col-md-6">
{{ beer_data | safe }}
</div>
</div> </div>
</div> </div>
</div> </div>

5
src/flask/test.py Normal file
View File

@ -0,0 +1,5 @@
import table.table_builder as table_builder
test = table_builder.TableBuilder()
print(test.table_build())