diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..1bae3c0 --- /dev/null +++ b/Pipfile @@ -0,0 +1,26 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +altair = "*" +certifi = "*" +flask = "*" +fortuna = "*" +gunicorn = "*" +jinja2 = "*" +joblib = "*" +jupyter = "*" +numpy = "*" +monsterlab = "*" +pandas = "*" +pymongo = {extras = ["srv"], version = "*"} +python-dotenv = "*" +scikit-learn = "*" +scipy = "*" + +[dev-packages] + +[requires] +python_version = "3.11" diff --git a/app/data.py b/app/data.py index 61e69e5..4dd1e1d 100644 --- a/app/data.py +++ b/app/data.py @@ -1,25 +1,69 @@ from os import getenv - from certifi import where from dotenv import load_dotenv from MonsterLab import Monster +import pandas as pd from pandas import DataFrame -from pymongo import MongoClient +from pymongo.mongo_client import MongoClient class Database: + """ + A class that is used to generate a randomized database of + monsters with different valued columns both numerical and text. + """ + + + load_dotenv() + + database = MongoClient(getenv("CONNECTION_STR"), + tlsCAFile=where())["Database"] + + def __init__(self, collection: str): + """ + Establishes a connection with our database. + """ + self.collection = self.database[collection] - def seed(self, amount): - pass + def seed(self, amount = 1000): + """ + Correctly inserts the specified number of monsters into the + collection. + """ + Monsters = [Monster().to_dict() for _ in range(amount)] + MonsterBook = self.collection.insert_many(Monsters) + return f"{MonsterBook.acknowledged}" def reset(self): - pass + """ + Correctly deletes all monsters from the collection. + """ + return f"{self.collection.delete_many(filter={}).acknowledged}" def count(self) -> int: - pass + """ + Correctly returns the number of monsters in the + collection. + """ + return self.collection.count_documents({}) def dataframe(self) -> DataFrame: - pass + """ + Correctly returns a DataFrame containing all monsters in + the collection. + """ + return pd.DataFrame(list(self.collection.find({}, {"_id":False}))) def html_table(self) -> str: - pass + """ + Correctly returns an HTML table representation of the + DataFrame or None if the collection is empty. + """ + if self.count() > 0: + return self.dataframe().to_html(index=False) + else: + return None + +if __name__ == '__main__': + db = Database("Collection") + db.seed() diff --git a/app/graph.py b/app/graph.py index 7fb68f1..928e915 100644 --- a/app/graph.py +++ b/app/graph.py @@ -1,5 +1,23 @@ -from altair import Chart +from altair import Chart, Tooltip +from pandas import DataFrame +from app.data import Database +def chart(df: DataFrame, x: str, y: str, target: str) -> Chart: + """ + Creates a chart based on our information from our data.py file. + """ + graph = Chart( + df, + title=f"{x} by {y} for {target}", + background = "gray" + ).mark_circle(size=100).encode( + x=x, + y=y, + color=target, + tooltip=Tooltip(df.columns.to_list()) + ) + return graph -def chart(df, x, y, target) -> Chart: - pass +if __name__ == '__main__': + collection_graph = Database("Collection") + collection_graph.seed() diff --git a/app/machine.py b/app/machine.py index 1785a57..aabbe58 100644 --- a/app/machine.py +++ b/app/machine.py @@ -1,17 +1,35 @@ -class Machine: +from pandas import DataFrame +from sklearn.ensemble import RandomForestClassifier +import joblib +from datetime import datetime + - def __init__(self, df): - pass +class Machine: + """ + This class should take in features from our Monsterbook and return a + prediction for the rarity of the monster based on input factors of + the other attributes of the monsters. + """ + + def __init__(self, df: DataFrame): + self.name = "Random Forest Classifier" + target = df["Rarity"] + features = df.drop(columns=["Rarity"]) + self.model = RandomForestClassifier() + self.model.fit(features, target) - def __call__(self, feature_basis): - pass + def __call__(self, pred_basis: DataFrame): + prediction, *_ = self.model.predict(pred_basis) + probability, *_ = self.model.predict_proba(pred_basis) + return prediction, max(probability) def save(self, filepath): - pass - - @staticmethod - def open(filepath): - pass + joblib.dump(self.model, filepath) + def open(self, filepath): + loaded_model = joblib.load(filepath) + return loaded_model + def info(self): - pass + ret_str = f"""Base Model: {self.name}\nTimestamp: {datetime.now()}""" + return ret_str diff --git a/app/main.py b/app/main.py index 1f9e0b0..dd530d4 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,5 @@ from base64 import b64decode import os - from Fortuna import random_int, random_float from MonsterLab import Monster from flask import Flask, render_template, request @@ -10,7 +9,7 @@ from app.graph import chart from app.machine import Machine -SPRINT = 0 +SPRINT = 3 APP = Flask(__name__) @@ -28,7 +27,7 @@ def home(): def data(): if SPRINT < 1: return render_template("data.html") - db = Database() + db = Database("Collection") return render_template( "data.html", count=db.count(), @@ -40,7 +39,7 @@ def data(): def view(): if SPRINT < 2: return render_template("view.html") - db = Database() + db = Database("Collection") options = ["Level", "Health", "Energy", "Sanity", "Rarity"] x_axis = request.values.get("x_axis") or options[1] y_axis = request.values.get("y_axis") or options[2] @@ -66,7 +65,7 @@ def view(): def model(): if SPRINT < 3: return render_template("model.html") - db = Database() + db = Database("Collection") options = ["Level", "Health", "Energy", "Sanity", "Rarity"] filepath = os.path.join("app", "model.joblib") if not os.path.exists(filepath): @@ -74,7 +73,9 @@ def model(): machine = Machine(df[options]) machine.save(filepath) else: - machine = Machine.open(filepath) + df = db.dataframe() + machine = Machine(df[options]) + machine.open(filepath) stats = [round(random_float(1, 250), 2) for _ in range(3)] level = request.values.get("level", type=int) or random_int(1, 20) health = request.values.get("health", type=float) or stats.pop() diff --git a/app/model.joblib b/app/model.joblib new file mode 100644 index 0000000..3a6e4e4 Binary files /dev/null and b/app/model.joblib differ diff --git a/fortuna-bin-win64 b/fortuna-bin-win64 new file mode 160000 index 0000000..1e873ee --- /dev/null +++ b/fortuna-bin-win64 @@ -0,0 +1 @@ +Subproject commit 1e873ee95f5adbd6b57216bbb51803986dd88c13