diff --git a/3-Web-App/1-Web-App/notebook.ipynb b/3-Web-App/1-Web-App/notebook.ipynb
index e69de29b..24e313fa 100644
--- a/3-Web-App/1-Web-App/notebook.ipynb
+++ b/3-Web-App/1-Web-App/notebook.ipynb
@@ -0,0 +1,427 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " datetime \n",
+ " city \n",
+ " state \n",
+ " country \n",
+ " shape \n",
+ " duration (seconds) \n",
+ " duration (hours/min) \n",
+ " comments \n",
+ " date posted \n",
+ " latitude \n",
+ " longitude \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 \n",
+ " 10/10/1949 20:30 \n",
+ " san marcos \n",
+ " tx \n",
+ " us \n",
+ " cylinder \n",
+ " 2700.0 \n",
+ " 45 minutes \n",
+ " This event took place in early fall around 194... \n",
+ " 4/27/2004 \n",
+ " 29.883056 \n",
+ " -97.941111 \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " 10/10/1949 21:00 \n",
+ " lackland afb \n",
+ " tx \n",
+ " NaN \n",
+ " light \n",
+ " 7200.0 \n",
+ " 1-2 hrs \n",
+ " 1949 Lackland AFB, TX. Lights racing acros... \n",
+ " 12/16/2005 \n",
+ " 29.384210 \n",
+ " -98.581082 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 10/10/1955 17:00 \n",
+ " chester (uk/england) \n",
+ " NaN \n",
+ " gb \n",
+ " circle \n",
+ " 20.0 \n",
+ " 20 seconds \n",
+ " Green/Orange circular disc over Chester, En... \n",
+ " 1/21/2008 \n",
+ " 53.200000 \n",
+ " -2.916667 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 10/10/1956 21:00 \n",
+ " edna \n",
+ " tx \n",
+ " us \n",
+ " circle \n",
+ " 20.0 \n",
+ " 1/2 hour \n",
+ " My older brother and twin sister were leaving ... \n",
+ " 1/17/2004 \n",
+ " 28.978333 \n",
+ " -96.645833 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " 10/10/1960 20:00 \n",
+ " kaneohe \n",
+ " hi \n",
+ " us \n",
+ " light \n",
+ " 900.0 \n",
+ " 15 minutes \n",
+ " AS a Marine 1st Lt. flying an FJ4B fighter/att... \n",
+ " 1/22/2004 \n",
+ " 21.418056 \n",
+ " -157.803611 \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " datetime city state country shape \\\n",
+ "0 10/10/1949 20:30 san marcos tx us cylinder \n",
+ "1 10/10/1949 21:00 lackland afb tx NaN light \n",
+ "2 10/10/1955 17:00 chester (uk/england) NaN gb circle \n",
+ "3 10/10/1956 21:00 edna tx us circle \n",
+ "4 10/10/1960 20:00 kaneohe hi us light \n",
+ "\n",
+ " duration (seconds) duration (hours/min) \\\n",
+ "0 2700.0 45 minutes \n",
+ "1 7200.0 1-2 hrs \n",
+ "2 20.0 20 seconds \n",
+ "3 20.0 1/2 hour \n",
+ "4 900.0 15 minutes \n",
+ "\n",
+ " comments date posted latitude \\\n",
+ "0 This event took place in early fall around 194... 4/27/2004 29.883056 \n",
+ "1 1949 Lackland AFB, TX. Lights racing acros... 12/16/2005 29.384210 \n",
+ "2 Green/Orange circular disc over Chester, En... 1/21/2008 53.200000 \n",
+ "3 My older brother and twin sister were leaving ... 1/17/2004 28.978333 \n",
+ "4 AS a Marine 1st Lt. flying an FJ4B fighter/att... 1/22/2004 21.418056 \n",
+ "\n",
+ " longitude \n",
+ "0 -97.941111 \n",
+ "1 -98.581082 \n",
+ "2 -2.916667 \n",
+ "3 -96.645833 \n",
+ "4 -157.803611 "
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "\n",
+ "ufos = pd.read_csv('./data/ufos.csv')\n",
+ "ufos.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array(['us', nan, 'gb', 'ca', 'au', 'de'], dtype=object)"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ufos = pd.DataFrame({'Seconds': ufos['duration (seconds)'], 'Country': ufos['country'],'Latitude': ufos['latitude'],'Longitude': ufos['longitude']})\n",
+ "\n",
+ "ufos.Country.unique()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Index: 25863 entries, 2 to 80330\n",
+ "Data columns (total 4 columns):\n",
+ " # Column Non-Null Count Dtype \n",
+ "--- ------ -------------- ----- \n",
+ " 0 Seconds 25863 non-null float64\n",
+ " 1 Country 25863 non-null object \n",
+ " 2 Latitude 25863 non-null float64\n",
+ " 3 Longitude 25863 non-null float64\n",
+ "dtypes: float64(3), object(1)\n",
+ "memory usage: 1010.3+ KB\n"
+ ]
+ }
+ ],
+ "source": [
+ "ufos.dropna(inplace=True)\n",
+ "\n",
+ "ufos = ufos[(ufos['Seconds'] >= 1) & (ufos['Seconds'] <= 60)]\n",
+ "\n",
+ "ufos.info()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " Seconds \n",
+ " Country \n",
+ " Latitude \n",
+ " Longitude \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " 20.0 \n",
+ " 3 \n",
+ " 53.200000 \n",
+ " -2.916667 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " 20.0 \n",
+ " 4 \n",
+ " 28.978333 \n",
+ " -96.645833 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 30.0 \n",
+ " 4 \n",
+ " 35.823889 \n",
+ " -80.253611 \n",
+ " \n",
+ " \n",
+ " 23 \n",
+ " 60.0 \n",
+ " 4 \n",
+ " 45.582778 \n",
+ " -122.352222 \n",
+ " \n",
+ " \n",
+ " 24 \n",
+ " 3.0 \n",
+ " 3 \n",
+ " 51.783333 \n",
+ " -0.783333 \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " Seconds Country Latitude Longitude\n",
+ "2 20.0 3 53.200000 -2.916667\n",
+ "3 20.0 4 28.978333 -96.645833\n",
+ "14 30.0 4 35.823889 -80.253611\n",
+ "23 60.0 4 45.582778 -122.352222\n",
+ "24 3.0 3 51.783333 -0.783333"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from sklearn.preprocessing import LabelEncoder\n",
+ "\n",
+ "ufos['Country'] = LabelEncoder().fit_transform(ufos['Country'])\n",
+ "\n",
+ "ufos.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sklearn.model_selection import train_test_split\n",
+ "\n",
+ "Selected_features = ['Seconds','Latitude','Longitude']\n",
+ "\n",
+ "X = ufos[Selected_features]\n",
+ "y = ufos['Country']\n",
+ "\n",
+ "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " precision recall f1-score support\n",
+ "\n",
+ " 0 1.00 1.00 1.00 41\n",
+ " 1 0.82 0.40 0.54 250\n",
+ " 2 1.00 0.88 0.93 8\n",
+ " 3 0.99 1.00 1.00 131\n",
+ " 4 0.97 1.00 0.98 4743\n",
+ "\n",
+ " accuracy 0.97 5173\n",
+ " macro avg 0.96 0.85 0.89 5173\n",
+ "weighted avg 0.96 0.97 0.96 5173\n",
+ "\n",
+ "Predicted labels: [4 4 4 ... 3 4 4]\n",
+ "Accuracy: 0.9665571235260004\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/Users/ray/Desktop/ML-For-Beginners/.venv/lib/python3.12/site-packages/sklearn/linear_model/_logistic.py:460: ConvergenceWarning: lbfgs failed to converge (status=1):\n",
+ "STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.\n",
+ "\n",
+ "Increase the number of iterations (max_iter) or scale the data as shown in:\n",
+ " https://scikit-learn.org/stable/modules/preprocessing.html\n",
+ "Please also refer to the documentation for alternative solver options:\n",
+ " https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression\n",
+ " n_iter_i = _check_optimize_result(\n"
+ ]
+ }
+ ],
+ "source": [
+ "from sklearn.metrics import accuracy_score, classification_report\n",
+ "from sklearn.linear_model import LogisticRegression\n",
+ "model = LogisticRegression()\n",
+ "model.fit(X_train, y_train)\n",
+ "predictions = model.predict(X_test)\n",
+ "\n",
+ "print(classification_report(y_test, predictions))\n",
+ "print('Predicted labels: ', predictions)\n",
+ "print('Accuracy: ', accuracy_score(y_test, predictions))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[1]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/Users/ray/Desktop/ML-For-Beginners/.venv/lib/python3.12/site-packages/sklearn/base.py:465: UserWarning: X does not have valid feature names, but LogisticRegression was fitted with feature names\n",
+ " warnings.warn(\n"
+ ]
+ }
+ ],
+ "source": [
+ "import pickle\n",
+ "model_filename = 'ufo-model.pkl'\n",
+ "pickle.dump(model, open(model_filename,'wb'))\n",
+ "\n",
+ "model = pickle.load(open('ufo-model.pkl','rb'))\n",
+ "print(model.predict([[50,44,-12]]))"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": ".venv",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/3-Web-App/1-Web-App/web-app/app.py b/3-Web-App/1-Web-App/web-app/app.py
new file mode 100644
index 00000000..9ee110cf
--- /dev/null
+++ b/3-Web-App/1-Web-App/web-app/app.py
@@ -0,0 +1,32 @@
+import numpy as np
+from flask import Flask, request, render_template
+import pickle
+
+app = Flask(__name__)
+
+model = pickle.load(open("./ufo-model.pkl", "rb"))
+
+
+@app.route("/")
+def home():
+ return render_template("index.html")
+
+
+@app.route("/predict", methods=["POST"])
+def predict():
+
+ int_features = [int(x) for x in request.form.values()]
+ final_features = [np.array(int_features)]
+ prediction = model.predict(final_features)
+
+ output = prediction[0]
+
+ countries = ["Australia", "Canada", "Germany", "UK", "US"]
+
+ return render_template(
+ "index.html", prediction_text="Likely country: {}".format(countries[output])
+ )
+
+
+if __name__ == "__main__":
+ app.run(debug=True)
\ No newline at end of file
diff --git a/3-Web-App/1-Web-App/web-app/requirements.txt b/3-Web-App/1-Web-App/web-app/requirements.txt
new file mode 100644
index 00000000..b0dd1377
--- /dev/null
+++ b/3-Web-App/1-Web-App/web-app/requirements.txt
@@ -0,0 +1,4 @@
+scikit-learn
+pandas
+numpy
+flask
\ No newline at end of file
diff --git a/3-Web-App/1-Web-App/web-app/static/styles.css b/3-Web-App/1-Web-App/web-app/static/styles.css
new file mode 100644
index 00000000..00ae9f9c
--- /dev/null
+++ b/3-Web-App/1-Web-App/web-app/static/styles.css
@@ -0,0 +1,29 @@
+body {
+ width: 100%;
+ height: 100%;
+ font-family: 'Helvetica';
+ background: black;
+ color: #fff;
+ text-align: center;
+ letter-spacing: 1.4px;
+ font-size: 30px;
+}
+
+input {
+ min-width: 150px;
+}
+
+.grid {
+ width: 300px;
+ border: 1px solid #2d2d2d;
+ display: grid;
+ justify-content: center;
+ margin: 20px auto;
+}
+
+.box {
+ color: #fff;
+ background: #2d2d2d;
+ padding: 12px;
+ display: inline-block;
+}
\ No newline at end of file
diff --git a/3-Web-App/1-Web-App/web-app/templates/index.html b/3-Web-App/1-Web-App/web-app/templates/index.html
new file mode 100644
index 00000000..265b866e
--- /dev/null
+++ b/3-Web-App/1-Web-App/web-app/templates/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+ 🛸 UFO Appearance Prediction! 👽
+
+
+
+
+
+
+
+
+
According to the number of seconds, latitude and longitude, which country is likely to have reported seeing a UFO?
+
+
+
+
{{ prediction_text }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/3-Web-App/1-Web-App/web-app/ufo-model.pkl b/3-Web-App/1-Web-App/web-app/ufo-model.pkl
new file mode 100644
index 00000000..907ea23a
Binary files /dev/null and b/3-Web-App/1-Web-App/web-app/ufo-model.pkl differ