diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..80d54f7
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index eb8f958..f8d2159 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
# ignore pycache
/__pycache__/*
/app/__pycache__/*
+/migrations/__pycache__/*
+/migrations/versions/__pycache__/*
# ignore vscode
/.vscode
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index cf4cd26..78fde2c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,3 @@
{
- "python.pythonPath": "/anaconda3/bin/python"
+ "python.pythonPath": "/Users/maxalminasatriakahfi/anaconda3/bin/python"
}
\ No newline at end of file
diff --git a/__pycache__/config.cpython-37.pyc b/__pycache__/config.cpython-37.pyc
index f1e95e4..d893a1f 100644
Binary files a/__pycache__/config.cpython-37.pyc and b/__pycache__/config.cpython-37.pyc differ
diff --git a/app/__pycache__/analytics.cpython-37.pyc b/app/__pycache__/analytics.cpython-37.pyc
index f23f56b..1f9fb4f 100644
Binary files a/app/__pycache__/analytics.cpython-37.pyc and b/app/__pycache__/analytics.cpython-37.pyc differ
diff --git a/app/__pycache__/forms.cpython-37.pyc b/app/__pycache__/forms.cpython-37.pyc
index 80cb4c6..eb358da 100644
Binary files a/app/__pycache__/forms.cpython-37.pyc and b/app/__pycache__/forms.cpython-37.pyc differ
diff --git a/app/__pycache__/models.cpython-37.pyc b/app/__pycache__/models.cpython-37.pyc
index 1531c26..4c4d0d9 100644
Binary files a/app/__pycache__/models.cpython-37.pyc and b/app/__pycache__/models.cpython-37.pyc differ
diff --git a/app/__pycache__/routes.cpython-37.pyc b/app/__pycache__/routes.cpython-37.pyc
index 63dc72e..e5add6e 100644
Binary files a/app/__pycache__/routes.cpython-37.pyc and b/app/__pycache__/routes.cpython-37.pyc differ
diff --git a/app/analytics.py b/app/analytics.py
index 10ed43f..d1b52e1 100644
--- a/app/analytics.py
+++ b/app/analytics.py
@@ -9,7 +9,7 @@
import altair as alt
import pandas as pd
import numpy as np
-import string, urllib, re, pickle
+import string, urllib, re
from sklearn.feature_extraction.text import TfidfVectorizer
from pathlib import Path
@@ -46,55 +46,30 @@ def getuserdb():
# ===================================================
def get_response():
- responses = pd.read_sql_query("SELECT response.workshop_id, response.satisfaction_score, response.comments, e.id as employee_id, w.workshop_name, w.workshop_start as timestamp\
- FROM response\
- LEFT JOIN workshop w ON w.id = response.workshop_id\
- LEFT JOIN employee e ON e.id = w.workshop_instructor", conn, parse_dates='timestamp')
-
- responses.loc[:,'comments'] = responses['comments'].astype(str)
- responses.loc[:,'satisfaction_score'] = responses['satisfaction_score'].astype(float)
-
+ responses = pd.read_sql_query("SELECT response.workshop_id, response.satisfaction_score, response.comments, e.id as employee_id, w.workshop_name, w.workshop_start as timestamp, response.sentiment\
+ FROM response\
+ LEFT JOIN workshop w ON w.id = response.workshop_id\
+ LEFT JOIN employee e ON e.id = w.workshop_instructor", conn, parse_dates='timestamp')
sixmonths = datetime.datetime.now() - datetime.timedelta(weeks=26)
responses = responses[responses.timestamp >= sixmonths]
+
+ responses = responses[responses['comments']!='-']
+ responses = responses.sort_values(by='timestamp', ascending=False).reset_index(drop=True)
+
return responses
def get_sentiment_data(responses):
- return responses[['workshop_id','workshop_name','employee_id','timestamp','comments']].copy()
+ return responses[['workshop_id','workshop_name','employee_id','timestamp','comments','sentiment']].copy()
def get_reviews_data(responses):
- return responses[['workshop_id', 'satisfaction_score', 'employee_id', 'timestamp']].copy()
-
-def process_sentiment(sentiment):
- my_model = load(str(Path().absolute())+'/model/sentiment/model.joblib')
- word_vector = load(str(Path().absolute())+'/model/sentiment/vector.joblib')
-
- sentiment['comments'].replace(['','None'], np.nan, inplace=True)
- sentiment.dropna(inplace=True)
- sentiment.loc[:,'comments'] = sentiment['comments'].apply(lambda x: x.lower())
- sentiment.loc[:,'comments'] = sentiment['comments'].apply(lambda x: x.translate(str.maketrans("","", string.punctuation)))
- sentiment.loc[:,'comments'] = sentiment['comments'].apply(lambda x: x.translate(str.maketrans("","", string.digits)))
- sentiment.loc[:,'comments'] = sentiment['comments'].apply(lambda x: re.sub(' +', ' ',x).strip())
- # word_vector.fit(pd.read_csv(str(Path().absolute())+'/model/sentiment/train.csv'))
-
- sentiment.loc[:,'score'] = ''
- sentiment.loc[:,'score'] = my_model.predict(word_vector.transform(sentiment['comments']))
-
- return sentiment
-
-def prereviews(reviews):
- reviews.fillna(3, inplace=True)
-
- return reviews
+ return responses[['workshop_id','satisfaction_score','employee_id','timestamp']].copy()
response = get_response()
sentiment = get_sentiment_data(response)
-sentiment = process_sentiment(sentiment)
-
reviews = get_reviews_data(response)
-reviews = prereviews(reviews)
domain = ['negative', 'positive']
colors = ['#7dbbd2cc', '#bbc6cbe6']
@@ -103,45 +78,44 @@ def get_params():
emp = getuserdb()
dat = emp.loc[emp.this_user == True,:].copy()
emp_id = dat.iloc[0]['workshop_instructor']
- sixmonths = datetime.datetime.now() - datetime.timedelta(weeks=26)
- return emp_id, sixmonths
+ return emp_id
def get_overall_sentiment():
- emp_id, sixmonths = get_params()
+ emp_id = get_params()
person = sentiment[sentiment.employee_id==emp_id].copy()
-
- return pd.crosstab([person['timestamp'],person['workshop_name'],person['workshop_id']],person['score']).apply(lambda x: round(x/x.sum()*100,2), axis=1).reset_index().melt(id_vars=['timestamp','workshop_name','workshop_id'])
+ monyear_percent = pd.crosstab([person['timestamp'],person['workshop_name'],person['workshop_id']],person['sentiment']).apply(lambda x: round(x/x.sum()*100,2), axis=1).reset_index().melt(id_vars=['timestamp','workshop_name','workshop_id'])
+
+ return monyear_percent.sort_values(by='timestamp', ascending=False).reset_index(drop=True)
def get_overall_reviews(monyear_percent):
- emp_id, sixmonths = get_params()
+ emp_id = get_params()
filter_idx = monyear_percent.groupby(['workshop_id'])['value'].transform(max) == monyear_percent['value']
al_reviews = monyear_percent[filter_idx].copy()
- al_reviews.sort_values('score', ascending=False, inplace=True)
+ al_reviews.sort_values('sentiment', ascending=False, inplace=True)
al_reviews.drop_duplicates(subset='workshop_id', keep="first", inplace=True)
- person_reviews = reviews[(reviews.employee_id==emp_id) & (reviews.timestamp >= sixmonths)]
+ person_reviews = reviews[(reviews.employee_id==emp_id)]
- sm_reviews = person_reviews[['satisfaction_score', 'workshop_id']].groupby(['workshop_id']).mean().round(1)
+ sm_reviews = person_reviews[['satisfaction_score', 'workshop_id', 'timestamp']].groupby(['workshop_id','timestamp']).mean().round(1)
sm_reviews.reset_index(inplace=True)
sm_reviews.loc[:,'workshop_name'] = sm_reviews['workshop_id'].map(al_reviews.set_index('workshop_id')['workshop_name'])
- sm_reviews.loc[:,'sentiment'] = sm_reviews['workshop_id'].map(al_reviews.set_index('workshop_id')['score'])
+ sm_reviews.loc[:,'sentiment'] = sm_reviews['workshop_id'].map(al_reviews.set_index('workshop_id')['sentiment'])
sm_reviews.loc[:,'value'] = sm_reviews['workshop_id'].map(al_reviews.set_index('workshop_id')['value'])
- return sm_reviews
+ return sm_reviews.sort_values(by='timestamp', ascending=False).reset_index(drop=True)
@app.route('/data/person_sentiment')
def vis_overall_sentiment():
monyear_percent = get_overall_sentiment()
chart = alt.Chart(monyear_percent).mark_bar().encode(
+ x=alt.X('workshop_name:N', axis=alt.Axis(title='Date'), sort=['timestamp:T']),
y=alt.Y('value:Q', axis=alt.Axis(title='Percentage (%)')),
- x=alt.X('workshop_name:N', axis=alt.Axis(title='Workshop Name')),
- color=alt.Color('score', scale=alt.Scale(domain=domain, range=colors), legend=alt.Legend(title="Sentiment")),
- order="timestamp:T",
- tooltip=[alt.Tooltip('value:Q', title="Percentage"), alt.Tooltip('score:N', title="Sentiment")]
+ color=alt.Color('sentiment', scale=alt.Scale(domain=domain, range=colors), legend=alt.Legend(title="Sentiment")),
+ tooltip=[alt.Tooltip('timestamp:T', title="Workshop Name"), alt.Tooltip('value:Q', title="Percentage"), alt.Tooltip('sentiment:N', title="Sentiment")]
).properties(
width=800, height=300
).configure_axis(grid=False)
@@ -641,9 +615,7 @@ def gettimenow():
Response.workshop_id.in_(w.id for w in workshops), Response.comments != '').join(
Workshop, isouter=True).order_by(
Workshop.workshop_start.desc()).paginate(
- per_page=30, page=1, error_out=True)
-
- # comments = pd.read_sql_query("SELECT workshop_id, comments FROM response", conn)
+ per_page=100, page=1, error_out=True)
monyear_percent = get_overall_sentiment()
diff --git a/app/forms.py b/app/forms.py
index 3291bbe..2b8384b 100644
--- a/app/forms.py
+++ b/app/forms.py
@@ -3,6 +3,7 @@
from wtforms.fields.html5 import IntegerRangeField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
from app.users import User
+from joblib import load
class LoginForm(FlaskForm):
email = StringField('Work Email', validators=[DataRequired(), Email()])
diff --git a/app/models.py b/app/models.py
index a2da9a5..f86ab72 100644
--- a/app/models.py
+++ b/app/models.py
@@ -69,3 +69,4 @@ class Response(db.Model):
venue_score = db.Column(db.Integer)
satisfaction_score = db.Column(db.Integer)
comments = db.Column(db.Text)
+ sentiment = db.Column(db.String(10), nullable=True)
diff --git a/app/routes.py b/app/routes.py
index 985493b..5b42d30 100644
--- a/app/routes.py
+++ b/app/routes.py
@@ -6,6 +6,7 @@
from app.models import Employee, Workshop, Response
from app.forms import LoginForm, RegistrationForm, SurveyForm, ResetPasswordRequestForm, ResetPasswordForm
from app.email import send_pw_reset_email
+from app.sentiment import preprocess_comments, predict_sentiment
from datetime import datetime
from sqlalchemy import func
@@ -142,7 +143,8 @@ def rate(workshop_id):
timeliness=form.time.data,
venue_score=form.venue.data,
satisfaction_score=form.satisfaction.data,
- comments=form.comments.data
+ comments=preprocess_comments(form.comments.data),
+ sentiment=predict_sentiment(preprocess_comments(form.comments.data))
)
db.session.add(response)
db.session.commit()
diff --git a/app/sentiment.py b/app/sentiment.py
new file mode 100644
index 0000000..ed8cea2
--- /dev/null
+++ b/app/sentiment.py
@@ -0,0 +1,18 @@
+from joblib import load
+from pathlib import Path
+import string, re
+
+def preprocess_comments(sentence):
+ sentence = sentence.lower()
+ sentence = sentence.translate(str.maketrans("","", string.punctuation))
+ sentence = sentence.translate(str.maketrans("","", string.digits))
+ sentence = re.sub(' +', ' ',sentence).strip()
+ if sentence == "": sentence = '-'
+
+ return sentence
+
+def predict_sentiment(sentence):
+ my_model = load(str(Path().absolute())+'/model/sentiment/model.joblib')
+ word_vector = load(str(Path().absolute())+'/model/sentiment/vector.joblib')
+
+ return str(my_model.predict(word_vector.transform([sentence]))[0])
\ No newline at end of file
diff --git a/app/templates/accomplishment.html b/app/templates/accomplishment.html
index b7a1161..8c4c832 100644
--- a/app/templates/accomplishment.html
+++ b/app/templates/accomplishment.html
@@ -157,7 +157,6 @@
rate_review Most Recent Reviews
{% endif %}
{% endfor %}
-
diff --git a/app/templates/sub/single_response.html b/app/templates/sub/single_response.html
index 296aab8..23af1e0 100644
--- a/app/templates/sub/single_response.html
+++ b/app/templates/sub/single_response.html
@@ -48,7 +48,7 @@ {{ review['sentiment'] }}
{% endif %}
{% for comments in personstats['qualitative'].items %}
- {% if (comments['workshop_id'] == review['workshop_id'] and comments['comments'] != '')%}
+ {% if (comments['workshop_id'] == review['workshop_id'] and comments['comments'] != '-')%}
{{comments['comments']}}
diff --git a/config.py b/config.py
index b25af6e..3d902e0 100644
--- a/config.py
+++ b/config.py
@@ -11,7 +11,7 @@
dburl = f'mysql+pymysql://{user}:{password}@{host}/{database}'
# create conditional connection
if(os.getenv('FLASK_ENV') == 'development'):
- conn = sqlite3.connect('test.db', check_same_thread=False)
+ conn = sqlite3.connect('test.db')
else:
conn = pymysql.connect(
host=host,
diff --git a/environment.yml b/environment.yml
index 3010aa3..bb9f9d7 100644
--- a/environment.yml
+++ b/environment.yml
@@ -15,6 +15,7 @@ dependencies:
- ipython_genutils=0.2.0=py36h241746c_0
- jedi=0.13.1=py36_0
- jinja2=2.10.1=py36_0
+- joblib=0.13.2=py36_0
- jupyter_core=4.4.0=py36_0
- libcxx=4.0.1=h579ed51_0
- libcxxabi=4.0.1=hebd6815_0
@@ -22,7 +23,6 @@ dependencies:
- libffi=3.2.1=h475c297_4
- libgfortran=3.0.1=h93005f0_2
- libsodium=1.0.16=h3efe00b_0
-- mkl=2019.0=118
- ncurses=6.1=h0a44026_0
- numpy=1.15.1=py36h6a91979_0
- numpy-base=1.15.1=py36h8a80b8c_0
@@ -89,6 +89,7 @@ dependencies:
- wheel=0.31.1=py36_1
- wrapt=1.10.11=py36_0
- wtforms=2.1=py36_0
+- autopep8=1.4.4=py_0
- blas=1.0=mkl
- bleach=3.1.0=py36_0
- cryptography=2.4.2=py36ha12b0ac_0
@@ -97,7 +98,10 @@ dependencies:
- libcurl=7.63.0=h051b688_1000
- libiconv=1.15=hdd342a3_7
- libssh2=1.8.0=ha12b0ac_4
+- llvm-openmp=4.0.1=hcfea43d_1
- mistune=0.8.4=py36h1de35cc_0
+- mkl=2019.4=233
+- mkl-service=2.0.2=py36h1de35cc_0
- nb_conda=2.2.1=py36_0
- nb_conda_kernels=2.2.0=py36_0
- nbconvert=5.3.1=py36_0
@@ -109,11 +113,13 @@ dependencies:
- perl=5.26.2=h4e221da_0
- pip=18.1=py36_0
- prometheus_client=0.5.0=py36_0
+- pycodestyle=2.5.0=py36_0
- python=3.6.8=haf84260_0
+- scikit-learn=0.21.2=py36h27c97d8_0
+- scipy=1.2.1=py36h1410ff5_0
- send2trash=1.5.0=py36_0
- sqlite=3.26.0=ha441bb4_0
- terminado=0.8.1=py36_1
- testpath=0.4.2=py36_0
- webencodings=0.5.1=py36_1
-prefix: /anaconda3/envs/pedagogy
-
+prefix: /anaconda3/envs/pedagogy
\ No newline at end of file
diff --git a/migrations/__pycache__/env.cpython-37.pyc b/migrations/__pycache__/env.cpython-37.pyc
index 0847bd3..bc8fd33 100644
Binary files a/migrations/__pycache__/env.cpython-37.pyc and b/migrations/__pycache__/env.cpython-37.pyc differ
diff --git a/migrations/env.py b/migrations/env.py
index 23663ff..79b8174 100644
--- a/migrations/env.py
+++ b/migrations/env.py
@@ -1,8 +1,12 @@
from __future__ import with_statement
-from alembic import context
-from sqlalchemy import engine_from_config, pool
-from logging.config import fileConfig
+
import logging
+from logging.config import fileConfig
+
+from sqlalchemy import engine_from_config
+from sqlalchemy import pool
+
+from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
@@ -18,8 +22,9 @@
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
-config.set_main_option('sqlalchemy.url',
- current_app.config.get('SQLALCHEMY_DATABASE_URI'))
+config.set_main_option(
+ 'sqlalchemy.url', current_app.config.get(
+ 'SQLALCHEMY_DATABASE_URI').replace('%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
@@ -41,7 +46,9 @@ def run_migrations_offline():
"""
url = config.get_main_option("sqlalchemy.url")
- context.configure(url=url)
+ context.configure(
+ url=url, target_metadata=target_metadata, literal_binds=True
+ )
with context.begin_transaction():
context.run_migrations()
@@ -65,21 +72,23 @@ def process_revision_directives(context, revision, directives):
directives[:] = []
logger.info('No changes in schema detected.')
- engine = engine_from_config(config.get_section(config.config_ini_section),
- prefix='sqlalchemy.',
- poolclass=pool.NullPool)
+ connectable = engine_from_config(
+ config.get_section(config.config_ini_section),
+ prefix='sqlalchemy.',
+ poolclass=pool.NullPool,
+ )
- connection = engine.connect()
- context.configure(connection=connection,
- target_metadata=target_metadata,
- process_revision_directives=process_revision_directives,
- **current_app.extensions['migrate'].configure_args)
+ with connectable.connect() as connection:
+ context.configure(
+ connection=connection,
+ target_metadata=target_metadata,
+ process_revision_directives=process_revision_directives,
+ **current_app.extensions['migrate'].configure_args
+ )
- try:
with context.begin_transaction():
context.run_migrations()
- finally:
- connection.close()
+
if context.is_offline_mode():
run_migrations_offline()
diff --git a/migrations/versions/429782854307_updated_models.py b/migrations/versions/429782854307_updated_models.py
index a8900b2..11ccc8e 100644
--- a/migrations/versions/429782854307_updated_models.py
+++ b/migrations/versions/429782854307_updated_models.py
@@ -1,9 +1,7 @@
"""updated models
-
Revision ID: 429782854307
Revises: 80dbe21e8059
Create Date: 2018-09-22 12:57:04.748750
-
"""
from alembic import op
import sqlalchemy as sa
@@ -58,4 +56,4 @@ def downgrade():
op.drop_table('response')
op.drop_table('assistants')
op.drop_table('workshop')
- # ### end Alembic commands ###
+ # ### end Alembic commands ###
\ No newline at end of file
diff --git a/migrations/versions/80dbe21e8059_initial_db_models.py b/migrations/versions/80dbe21e8059_initial_db_models.py
index 7631372..3f01e96 100644
--- a/migrations/versions/80dbe21e8059_initial_db_models.py
+++ b/migrations/versions/80dbe21e8059_initial_db_models.py
@@ -18,59 +18,60 @@
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
- op.create_table('user',
- sa.Column('id', sa.Integer(), nullable=False),
- sa.Column('email', sa.String(length=64), nullable=True),
- sa.Column('password_hash', sa.String(length=128), nullable=True),
- sa.Column('last_seen', sa.DateTime(), nullable=True),
- sa.Column('leadership', sa.Boolean(), nullable=False),
- sa.PrimaryKeyConstraint('id')
- )
- op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
- op.create_table('employee',
- sa.Column('id', sa.Integer(), nullable=False),
- sa.Column('email', sa.String(length=64), nullable=True),
- sa.Column('name', sa.String(length=64), nullable=True),
- sa.Column('join_date', sa.Date(), nullable=True),
- sa.Column('active', sa.Boolean(), nullable=False),
- sa.Column('degree', sa.String(length=32), nullable=True),
- sa.Column('university', sa.String(length=64), nullable=True),
- sa.ForeignKeyConstraint(['email'], ['user.email'], ),
- sa.PrimaryKeyConstraint('id'),
- sa.UniqueConstraint('name')
- )
- op.create_table('workshop',
- sa.Column('id', sa.Integer(), nullable=False),
- sa.Column('workshop_name', sa.String(length=64), nullable=True),
- sa.Column('workshop_category', sa.Enum('Academy', 'DSS', 'Corporate', 'Others', name='workshop_category'), nullable=False),
- sa.Column('workshop_instructor', sa.Integer(), nullable=False),
- sa.Column('workshop_start', sa.DateTime(), nullable=True),
- sa.Column('workshop_hours', sa.Integer(), nullable=True),
- sa.Column('workshop_venue', sa.String(length=64), nullable=True),
- sa.Column('class_size', sa.Integer(), nullable=True),
- sa.ForeignKeyConstraint(['workshop_instructor'], ['employee.id'], ),
- sa.PrimaryKeyConstraint('id')
- )
- op.create_table('assistants',
- sa.Column('employee_id', sa.Integer(), nullable=True),
- sa.Column('workshop_id', sa.Integer(), nullable=True),
- sa.ForeignKeyConstraint(['employee_id'], ['employee.id'], ),
- sa.ForeignKeyConstraint(['workshop_id'], ['workshop.id'], )
- )
- op.create_table('response',
- sa.Column('id', sa.Integer(), nullable=False),
- sa.Column('workshop_id', sa.Integer(), nullable=False),
- sa.Column('difficulty', sa.Integer(), nullable=True),
- sa.Column('assistants_score', sa.Integer(), nullable=True),
- sa.Column('knowledge', sa.Integer(), nullable=True),
- sa.Column('objectives', sa.Integer(), nullable=True),
- sa.Column('timeliness', sa.Integer(), nullable=True),
- sa.Column('venue_score', sa.Integer(), nullable=True),
- sa.Column('satisfaction_score', sa.Integer(), nullable=True),
- sa.Column('comments', sa.Text(), nullable=True),
- sa.ForeignKeyConstraint(['workshop_id'], ['workshop.id'], ),
- sa.PrimaryKeyConstraint('id')
- )
+ pass
+ # op.create_table('user',
+ # sa.Column('id', sa.Integer(), nullable=False),
+ # sa.Column('email', sa.String(length=64), nullable=True),
+ # sa.Column('password_hash', sa.String(length=128), nullable=True),
+ # sa.Column('last_seen', sa.DateTime(), nullable=True),
+ # sa.Column('leadership', sa.Boolean(), nullable=False),
+ # sa.PrimaryKeyConstraint('id')
+ # )
+ # op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
+ # op.create_table('employee',
+ # sa.Column('id', sa.Integer(), nullable=False),
+ # sa.Column('email', sa.String(length=64), nullable=True),
+ # sa.Column('name', sa.String(length=64), nullable=True),
+ # sa.Column('join_date', sa.Date(), nullable=True),
+ # sa.Column('active', sa.Boolean(), nullable=False),
+ # sa.Column('degree', sa.String(length=32), nullable=True),
+ # sa.Column('university', sa.String(length=64), nullable=True),
+ # sa.ForeignKeyConstraint(['email'], ['user.email'], ),
+ # sa.PrimaryKeyConstraint('id'),
+ # sa.UniqueConstraint('name')
+ # )
+ # op.create_table('workshop',
+ # sa.Column('id', sa.Integer(), nullable=False),
+ # sa.Column('workshop_name', sa.String(length=64), nullable=True),
+ # sa.Column('workshop_category', sa.Enum('Academy', 'DSS', 'Corporate', 'Others', name='workshop_category'), nullable=False),
+ # sa.Column('workshop_instructor', sa.Integer(), nullable=False),
+ # sa.Column('workshop_start', sa.DateTime(), nullable=True),
+ # sa.Column('workshop_hours', sa.Integer(), nullable=True),
+ # sa.Column('workshop_venue', sa.String(length=64), nullable=True),
+ # sa.Column('class_size', sa.Integer(), nullable=True),
+ # sa.ForeignKeyConstraint(['workshop_instructor'], ['employee.id'], ),
+ # sa.PrimaryKeyConstraint('id')
+ # )
+ # op.create_table('assistants',
+ # sa.Column('employee_id', sa.Integer(), nullable=True),
+ # sa.Column('workshop_id', sa.Integer(), nullable=True),
+ # sa.ForeignKeyConstraint(['employee_id'], ['employee.id'], ),
+ # sa.ForeignKeyConstraint(['workshop_id'], ['workshop.id'], )
+ # )
+ # op.create_table('response',
+ # sa.Column('id', sa.Integer(), nullable=False),
+ # sa.Column('workshop_id', sa.Integer(), nullable=False),
+ # sa.Column('difficulty', sa.Integer(), nullable=True),
+ # sa.Column('assistants_score', sa.Integer(), nullable=True),
+ # sa.Column('knowledge', sa.Integer(), nullable=True),
+ # sa.Column('objectives', sa.Integer(), nullable=True),
+ # sa.Column('timeliness', sa.Integer(), nullable=True),
+ # sa.Column('venue_score', sa.Integer(), nullable=True),
+ # sa.Column('satisfaction_score', sa.Integer(), nullable=True),
+ # sa.Column('comments', sa.Text(), nullable=True),
+ # sa.ForeignKeyConstraint(['workshop_id'], ['workshop.id'], ),
+ # sa.PrimaryKeyConstraint('id')
+ # )
# ### end Alembic commands ###
@@ -82,4 +83,4 @@ def downgrade():
op.drop_table('employee')
op.drop_index(op.f('ix_user_email'), table_name='user')
op.drop_table('user')
- # ### end Alembic commands ###
+ # ### end Alembic commands ###
\ No newline at end of file
diff --git a/migrations/versions/__pycache__/429782854307_updated_models.cpython-37.pyc b/migrations/versions/__pycache__/429782854307_updated_models.cpython-37.pyc
deleted file mode 100644
index fcdbbd1..0000000
Binary files a/migrations/versions/__pycache__/429782854307_updated_models.cpython-37.pyc and /dev/null differ
diff --git a/migrations/versions/__pycache__/80dbe21e8059_initial_db_models.cpython-37.pyc b/migrations/versions/__pycache__/80dbe21e8059_initial_db_models.cpython-37.pyc
deleted file mode 100644
index 70dd8de..0000000
Binary files a/migrations/versions/__pycache__/80dbe21e8059_initial_db_models.cpython-37.pyc and /dev/null differ
diff --git a/migrations/versions/b7a1e1a27088_add_sentiment_column.py b/migrations/versions/b7a1e1a27088_add_sentiment_column.py
new file mode 100644
index 0000000..59151df
--- /dev/null
+++ b/migrations/versions/b7a1e1a27088_add_sentiment_column.py
@@ -0,0 +1,87 @@
+"""add sentiment column
+
+Revision ID: b7a1e1a27088
+Revises: 429782854307
+Create Date: 2019-07-16 18:18:04.434145
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'b7a1e1a27088'
+down_revision = '429782854307'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('user',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('email', sa.String(length=64), nullable=True),
+ sa.Column('password_hash', sa.String(length=128), nullable=True),
+ sa.Column('last_seen', sa.DateTime(), nullable=True),
+ sa.Column('leadership', sa.Boolean(), nullable=False),
+ sa.Column('analyst', sa.Boolean(), nullable=False),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
+ op.create_table('employee',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('email', sa.String(length=64), nullable=True),
+ sa.Column('name', sa.String(length=64), nullable=True),
+ sa.Column('join_date', sa.Date(), nullable=True),
+ sa.Column('active', sa.Boolean(), nullable=False),
+ sa.Column('degree', sa.String(length=32), nullable=True),
+ sa.Column('university', sa.String(length=64), nullable=True),
+ sa.ForeignKeyConstraint(['email'], ['user.email'], ),
+ sa.PrimaryKeyConstraint('id'),
+ sa.UniqueConstraint('name')
+ )
+ op.create_table('workshop',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('workshop_name', sa.String(length=64), nullable=True),
+ sa.Column('workshop_category', sa.Enum('Academy', 'DSS', 'Corporate', 'Weekend', 'Others', name='workshop_category'), nullable=False),
+ sa.Column('workshop_instructor', sa.Integer(), nullable=False),
+ sa.Column('workshop_start', sa.DateTime(), nullable=False),
+ sa.Column('workshop_hours', sa.Integer(), nullable=False),
+ sa.Column('workshop_venue', sa.String(length=64), nullable=False),
+ sa.Column('class_size', sa.Integer(), nullable=False),
+ sa.ForeignKeyConstraint(['workshop_instructor'], ['employee.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ op.create_table('assistants',
+ sa.Column('employee_id', sa.Integer(), nullable=True),
+ sa.Column('workshop_id', sa.Integer(), nullable=True),
+ sa.ForeignKeyConstraint(['employee_id'], ['employee.id'], ),
+ sa.ForeignKeyConstraint(['workshop_id'], ['workshop.id'], )
+ )
+ op.create_table('response',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('workshop_id', sa.Integer(), nullable=False),
+ sa.Column('difficulty', sa.Integer(), nullable=True),
+ sa.Column('assistants_score', sa.Integer(), nullable=True),
+ sa.Column('knowledge', sa.Integer(), nullable=True),
+ sa.Column('objectives', sa.Integer(), nullable=True),
+ sa.Column('timeliness', sa.Integer(), nullable=True),
+ sa.Column('venue_score', sa.Integer(), nullable=True),
+ sa.Column('satisfaction_score', sa.Integer(), nullable=True),
+ sa.Column('comments', sa.Text(), nullable=True),
+ sa.Column('sentiment', sa.String(length=10), nullable=True),
+ sa.ForeignKeyConstraint(['workshop_id'], ['workshop.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('response')
+ op.drop_table('assistants')
+ op.drop_table('workshop')
+ op.drop_table('employee')
+ op.drop_index(op.f('ix_user_email'), table_name='user')
+ op.drop_table('user')
+ # ### end Alembic commands ###
diff --git a/model/.DS_Store b/model/.DS_Store
new file mode 100644
index 0000000..6e18005
Binary files /dev/null and b/model/.DS_Store differ
diff --git a/model/sentiment/model.joblib b/model/sentiment/model.joblib
index ef99d6b..a3d41b8 100644
Binary files a/model/sentiment/model.joblib and b/model/sentiment/model.joblib differ
diff --git a/model/sentiment/vector.joblib b/model/sentiment/vector.joblib
index 9f7b59d..aa7334a 100644
Binary files a/model/sentiment/vector.joblib and b/model/sentiment/vector.joblib differ
diff --git a/requirements.txt b/requirements.txt
index e7c558d..634d07e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,14 +1,16 @@
-# pip freeze > requirements.txt
alembic==0.9.9
-altair==3.1.0
+altair==2.2.2
+appdirs==1.4.3
appnope==0.1.0
arrow==0.12.1
asn1crypto==0.24.0
astroid==2.0.2
+attrs==19.1.0
+autopep8==1.4.4
backcall==0.1.0
bleach==3.1.0
blinker==1.4
-certifi==2018.8.24
+certifi==2019.6.16
cffi==1.11.5
click==6.7
cryptography==2.4.2
@@ -19,43 +21,30 @@ Flask-Admin==1.5.3
Flask-Caching==1.6.0
Flask-Login==0.4.1
Flask-Mail==0.9.1
-Flask-Migrate==2.2.1
+Flask-Migrate==2.5.2
Flask-SQLAlchemy==2.3.2
Flask-WTF==0.14.2
idna==2.7
-# ipykernel==5.1.0
-# ipython==7.1.1
-# ipython-genutils==0.2.0
+ipython-genutils==0.2.0
isort==4.3.4
itsdangerous==0.24
jedi==0.13.1
-Jinja2>=2.10.1
+Jinja2==2.10.1
+joblib==0.13.2
jsonschema==2.6.0
-# jupyter-client==5.2.3
-# jupyter-core==4.4.0
lazy-object-proxy==1.3.1
Mako==1.0.7
MarkupSafe==1.0
-matplotlib==3.1.0
mccabe==0.6.1
-mistune==0.8.4
-# mkl-fft==1.0.6
-# mkl-random>=1.0.1
-# nb-conda==2.2.1
-# nb-conda-kernels==2.2.0
-# nbconvert==5.3.1
-# nbformat==4.4.0
-# notebook==5.7.4
-nltk==3.4.1
numpy==1.15.1
pandas==0.24.2
-pandocfilters==1.4.2
parso==0.3.1
pexpect==4.6.0
pickleshare==0.7.5
prometheus-client==0.5.0
prompt-toolkit==2.0.7
ptyprocess==0.6.0
+pycodestyle==2.5.0
pycparser==2.18
Pygments==2.2.0
PyJWT==1.7.1
@@ -66,17 +55,20 @@ python-dotenv==0.9.1
python-editor==1.0.3
pytz==2018.5
pyzmq==17.1.2
+scikit-learn==0.21.2
+scipy==1.2.1
Send2Trash==1.5.0
six==1.11.0
-SQLAlchemy==1.2.11
+SQLAlchemy==1.3.3
terminado==0.8.1
testpath==0.4.2
-textblob==0.15.2
+toml==0.10.0
toolz==0.9.0
tornado==5.1.1
traitlets==4.3.2
typed-ast==1.1.0
-# vega-datasets==0.5.0
+typing==3.7.4
+vega-datasets==0.5.0
wcwidth==0.1.7
webencodings==0.5.1
Werkzeug==0.14.1
diff --git a/sm.py b/sm.py
new file mode 100644
index 0000000..511fe94
--- /dev/null
+++ b/sm.py
@@ -0,0 +1,28 @@
+# this file is to clean the responses in database
+
+from joblib import load
+from pathlib import Path
+
+import pandas as pd
+import numpy as np
+import sqlite3, string, re
+
+conn = sqlite3.connect('test.db')
+
+my_model = load(str(Path().absolute())+'/model/sentiment/model.joblib')
+word_vector = load(str(Path().absolute())+'/model/sentiment/vector.joblib')
+
+rs = pd.read_sql_query("SELECT * FROM response", conn)
+
+rs.loc[:,'sentiment'] = '-'
+rs.loc[:,'comments'] = rs['comments'].astype(str)
+rs.loc[:,'comments'] = rs['comments'].apply(lambda x: x.lower())
+rs.loc[:,'comments'] = rs['comments'].apply(lambda x: x.translate(str.maketrans("","", string.punctuation)))
+rs.loc[:,'comments'] = rs['comments'].apply(lambda x: x.translate(str.maketrans("","", string.digits)))
+rs.loc[:,'comments'] = rs['comments'].apply(lambda x: re.sub(' +', ' ',x).strip())
+rs['comments'].replace(['','None'], np.nan, inplace=True)
+rs['comments'].fillna('-',inplace=True)
+rs.loc[:,'sentiment'] = my_model.predict(word_vector.transform(rs['comments']))
+
+# write to sql after data cleansing
+# rs.to_sql('response', con=conn, if_exists='replace', index=False)
\ No newline at end of file
diff --git a/test.db b/test.db
index 49e2616..0e2ccdd 100644
Binary files a/test.db and b/test.db differ