diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd49ae9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +app/__pycache__ diff --git a/app/__init__.py b/app/__init__.py index 744c94f..8e6232f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -17,6 +17,7 @@ # create conditional connection if(os.getenv('FLASK_ENV') == 'development'): + app.config.from_object(Development) conn = sqlite3.connect('test.db') app.config.from_object(Development) else: diff --git a/app/analytics.py b/app/analytics.py index 4a738f4..8b79f07 100644 --- a/app/analytics.py +++ b/app/analytics.py @@ -3,10 +3,12 @@ from app import app, cache, conn from app.models import Employee, Workshop, Response import altair as alt -from altair import expr, datum +from altair import expr, datum, Scale import pandas as pd import datetime as datetime - +import pymysql +from config import host, user, password, database +from sqlalchemy import func @cache.cached(timeout=60*60, key_prefix='hourly_db') def getdb(): @@ -19,7 +21,7 @@ def getdb(): return pd.read_sql_query( "SELECT workshop.id, workshop_name, workshop_category, workshop_instructor, \ - workshop_start, workshop_hours, class_size, e.name, e.active, e.university \ + workshop_start, workshop_hours, class_size, e.name, e.email, e.active, e.university \ FROM workshop \ LEFT JOIN employee as e ON e.id = workshop.workshop_instructor", conn, index_col='id', parse_dates='workshop_start') @@ -46,34 +48,40 @@ def getuserdb(): @cache.cached(timeout=86400, key_prefix='accum_g') def accum_global(): dat = df.copy() - dat = dat.append({'workshop_start': datetime.datetime.now(), 'workshop_category': 'Corporate'}, ignore_index=True) - dat = dat.append({'workshop_start': datetime.datetime.now(), 'workshop_category': 'Academy'}, ignore_index=True) - dat['workshop_category'] = dat['workshop_category'].apply(lambda x: 'Corporate' if (x == 'Corporate') else 'Public').astype('category') - dat = dat.loc[:,['workshop_start', 'workshop_category', 'workshop_hours', 'class_size']]\ - .set_index('workshop_start')\ - .groupby('workshop_category')\ - .resample('W').sum().reset_index() - dat['workshop hours']=dat.groupby(['workshop_category'])['workshop_hours'].cumsum() - dat['students']=dat.groupby(['workshop_category'])['class_size'].cumsum() - dat = dat.melt(id_vars=['workshop_start', 'workshop_category'],value_vars=['workshop hours', 'students']) + dat['workshop_start'] = pd.to_datetime(dat['workshop_start']).dt.floor('D') + idx = pd.date_range(dat['workshop_start'].min(), dat['workshop_start'].max()) + dat = dat[dat.workshop_category.isin(['Academy', 'Corporate'])] + cum_size = dat.groupby(['workshop_category', 'workshop_start'])['class_size'].sum() + dat = cum_size.reindex(pd.MultiIndex.from_product([cum_size.index.levels[0], idx], names=['category', 'date']), fill_value=0).reset_index() + dat['cumulative'] = dat.groupby('category').cumsum() - chart = alt.Chart(dat).mark_area().encode( - column=alt.Column('workshop_category', title=None, sort="descending", - header=alt.Header( - labelColor='#ffffff', - titleAnchor="start")), - x=alt.X("workshop_start", title="Date"), - y=alt.Y("value:Q", title="Cumulative"), - color=alt.Color("variable", + brush = alt.selection(type='interval', encodings=['x']) + + # Create a stacked chart area + upper = alt.Chart(dat).mark_area().encode( + x=alt.X("date", title="Date", scale={'domain': brush.ref()}), + y=alt.Y("cumulative:Q", title="Cumulative"), + color=alt.Color("category", scale=alt.Scale( range=['#7dbbd2cc', '#bbc6cbe6']), - legend=None - ), - tooltip=['variable', 'value:Q'] - ).properties(width=350).configure_axis( - labelColor='#bbc6cbe6', - titleColor='#bbc6cbe6', + legend=alt.Legend(title='Workshop Category') + ) + ) + lower = alt.Chart().mark_area(color='#75b3cacc').encode( + x=alt.X("date", axis=alt.Axis(title=''), scale={ + 'domain':brush.ref() + }), + y=alt.Y("cumulative", axis=alt.Axis(title='')) + ).properties( + height=30 + ).add_selection( + brush + ) + + chart = alt.vconcat(upper,lower, data=dat).configure_view( + strokeWidth=0 + ).configure_axis( grid=False ) @@ -152,6 +160,123 @@ def punchcode(): ) return chart.to_json() +@app.route('/data/calendar_heatmap') +@cache.cached(timeout=86400, key_prefix='cal_heatmap') +def calendar_heatmap(): + dat = df.copy() + dat = dat[dat.name!='Capstone'] + dat['workshop_start'] = pd.to_datetime(dat['workshop_start']) + dat['weekly'] = dat.workshop_start.dt.to_period('W') + # Set visualization as 'This year in a glimpse' + start_week = pd.Period(datetime.datetime.now().year, 'W-SUN') + end_week = pd.Period(datetime.datetime.now().year+1, 'W-SUN') + dat = dat[(dat['weekly'] >= start_week) & (dat['weekly'] <= end_week)] + + dat = dat[dat.workshop_category.isin(['Academy', 'Corporate'])].groupby(['weekly', 'name', 'workshop_category'])['workshop_hours'].sum() + + + + # Create academy workshop hour value + aca = dat.unstack(fill_value=0).unstack(fill_value=0).xs('Academy', axis=1).\ + reindex(pd.PeriodIndex(start=start_week, end=end_week, freq='W'), fill_value=0).\ + reset_index().melt(id_vars='index') + aca['index'] = aca['index'].dt.to_timestamp(how='S') + + # Create corporate workshop hour value + cor = dat.unstack(fill_value=0).unstack(fill_value=0).xs('Corporate', axis=1).\ + reindex(pd.PeriodIndex(start=start_week, end=end_week, freq='W'), fill_value=0).\ + reset_index().melt(id_vars='index') + cor['index'] = cor['index'].dt.to_timestamp(how='S') + + # Set color domain and range + domain = [int(cor.value.min()), int(cor.value.max())] + range_ = ['#adbac0', '#4d9dcc'] + + ## Create academy calendar + base_aca = alt.Chart(aca).encode( + alt.X('index', axis=alt.Axis(title='Date'), scale=alt.Scale(padding=20)), + alt.Y('name', axis=alt.Axis(title=''), + scale=alt.Scale(padding=20), + sort=alt.EncodingSortField( + field="value", # The field to use for the sort + op="sum", # The operation to run on the field prior to sorting + order="descending" # The order to sort in + )) + ) + + # Configure heatmap + heatmap_aca = base_aca.mark_square(size=300).encode( + color=alt.condition( + alt.datum.value > 1, + alt.Color('value:Q', + scale=alt.Scale( + domain=domain, + range=range_), + legend=None), + alt.value(None) + ) + ).properties( + width=1000, + height=500, + title='Academy Workshops Contribution' + ) + + ## Create corporate calendar + base_cor = alt.Chart(cor).encode( + alt.X('index', axis=alt.Axis(title='Date'), scale=alt.Scale(padding=20)), + alt.Y( + field='name', + axis=alt.Axis(title=''), + scale=alt.Scale(padding=20), + type='nominal', + sort=alt.EncodingSortField( + field="value", # The field to use for the sort + op="sum", # The operation to run on the field prior to sorting + order="descending" # The order to sort in + )) + ) + + # Configure heatmap + heatmap_cor = base_cor.mark_square(size=300).encode( + color=alt.condition( + alt.datum.value > 1, + alt.Color('value:Q', + scale=alt.Scale( + domain=domain, + range=range_), + legend=alt.Legend( + direction='vertical', + title='Workshop Hours', + titleColor='#bbc6cbe6')), + alt.value(None) + ) + ).properties( + width=1000, + height=500, + title='Corporate Workshops Contribution' + ) + + # Draw the chart + chart = alt.hconcat(heatmap_aca, heatmap_cor).configure_axis( + labelColor='#bbc6cbe6', + titleColor='#bbc6cbe6', + grid=False, + labelFontSize=16, + titleFontSize=24 + ).configure_title( + fontSize=26, + anchor='start', + color='#bbc6cbe6' + ).configure_legend( + titleColor='#bbc6cbe6', + labelColor='#bbc6cbe6', + titleFontSize=16, + labelFontSize=16 + ) + + return chart.to_json() + + @app.route('/data/category_bars') @cache.cached(timeout=86400, key_prefix='c_b') def category_bars(): @@ -421,25 +546,36 @@ def team_leadinst_line(): # # =================================================== # =================================================== -@cache.cached(timeout=43200, key_prefix='gt_stats') +# @cache.cached(timeout=43200, key_prefix='gt_stats') def factory_homepage(): + + total_hours = func.sum(Workshop.workshop_hours).label('total_hours') + total_students = func.sum(Workshop.class_size).label('total_students') + topten = Employee.query.with_entities(Employee.email, Employee.name, total_hours, total_students).filter( + Employee.active == 1, Employee.name != 'Capstone').join( + Workshop, isouter=True).group_by( + Employee.name).order_by(total_hours.desc()).paginate( + per_page=10, page=1, error_out=True) + stats = { 'students': df['class_size'].sum(), 'workshops': df.shape[0], 'studenthours': sum(df['workshop_hours']), # 'studenthours': sum(df['workshop_hours'] * df['class_size']), 'companies': sum(df['workshop_category'] == 'Corporate'), + 'registered': df[df['name'] != 'Capstone'].loc[:,['name','email']].drop_duplicates(), 'instructors': len(df['workshop_instructor'].unique()), - 'topten': df[df.name != 'Capstone'].loc[:,['name','workshop_hours', 'class_size']].groupby( - 'name').sum().sort_values( - by='class_size', - ascending=False) - .head(10) - .rename_axis(None) - .rename( - columns={'workshop_hours':'Total Hours', - 'class_size':'Total Students'}) - .to_html(classes=['table thead-light table-striped table-bordered table-hover table-sm']) + 'topten': topten + # 'topten': df[df.name != 'Capstone'].loc[:,['name','workshop_hours', 'class_size']].groupby( + # 'name').sum().sort_values( + # by='class_size', + # ascending=False) + # .head(10) + # .rename_axis(None) + # .rename( + # columns={'workshop_hours':'Total Hours', + # 'class_size':'Total Students'}) + # .to_html(classes=['table thead-light table-striped table-bordered table-hover table-sm']) } return stats @@ -547,4 +683,4 @@ def gettimenow(): 'updatewhen': gettimenow(), } - return stats + return stats \ No newline at end of file diff --git a/app/static/css/pedagogy.css b/app/static/css/pedagogy.css index 6bbaf67..1e5129e 100644 --- a/app/static/css/pedagogy.css +++ b/app/static/css/pedagogy.css @@ -176,7 +176,7 @@ ul.timeline > li:before { p.card-text { color: white; - font-weight: bolder; + font-weight: normal; } .blockquote-footer { @@ -330,4 +330,17 @@ dd { border: none; padding: 1% 2.5%; color: white; +} + +table, +td, +th { + padding: 1px; + border: 1px solid #b5c1c7; +} +img.image { + width: 3%; + height: auto; + border-radius: 50%; + margin-right: 1%; } \ No newline at end of file diff --git a/app/static/img/instructors/Andy_OH@np.edu.sg.jpg b/app/static/img/instructors/Andy_OH@np.edu.sg.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/Andy_OH@np.edu.sg.jpg differ diff --git a/app/static/img/instructors/Charis_TANG@np.edu.sg.jpg b/app/static/img/instructors/Charis_TANG@np.edu.sg.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/Charis_TANG@np.edu.sg.jpg differ diff --git a/app/static/img/instructors/None.jpg b/app/static/img/instructors/None.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/None.jpg differ diff --git a/app/static/img/instructors/Ricky_CHUA@np.edu.sg.jpg b/app/static/img/instructors/Ricky_CHUA@np.edu.sg.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/Ricky_CHUA@np.edu.sg.jpg differ diff --git a/app/static/img/instructors/ahmad@algorit.ma.JPG b/app/static/img/instructors/ahmad@algorit.ma.JPG new file mode 100644 index 0000000..a62e9da Binary files /dev/null and b/app/static/img/instructors/ahmad@algorit.ma.JPG differ diff --git a/app/static/img/instructors/ajeng@algorit.ma.JPG b/app/static/img/instructors/ajeng@algorit.ma.JPG new file mode 100644 index 0000000..3986043 Binary files /dev/null and b/app/static/img/instructors/ajeng@algorit.ma.JPG differ diff --git a/app/static/img/instructors/ardhito@algorit.ma.jpg b/app/static/img/instructors/ardhito@algorit.ma.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/ardhito@algorit.ma.jpg differ diff --git a/app/static/img/instructors/arga@algorit.ma.jpg b/app/static/img/instructors/arga@algorit.ma.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/arga@algorit.ma.jpg differ diff --git a/app/static/img/instructors/bagas@algorit.ma.JPG b/app/static/img/instructors/bagas@algorit.ma.JPG new file mode 100644 index 0000000..dade0a8 Binary files /dev/null and b/app/static/img/instructors/bagas@algorit.ma.JPG differ diff --git a/app/static/img/instructors/david@algorit.ma.jpg b/app/static/img/instructors/david@algorit.ma.jpg new file mode 100644 index 0000000..070bca5 Binary files /dev/null and b/app/static/img/instructors/david@algorit.ma.jpg differ diff --git a/app/static/img/instructors/fafilia@algorit.ma.jpg b/app/static/img/instructors/fafilia@algorit.ma.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/fafilia@algorit.ma.jpg differ diff --git a/app/static/img/instructors/handoyo@algorit.ma.jpg b/app/static/img/instructors/handoyo@algorit.ma.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/handoyo@algorit.ma.jpg differ diff --git a/app/static/img/instructors/iffa@algorit.ma.jpg b/app/static/img/instructors/iffa@algorit.ma.jpg new file mode 100644 index 0000000..9936eb6 Binary files /dev/null and b/app/static/img/instructors/iffa@algorit.ma.jpg differ diff --git a/app/static/img/instructors/ilma@algorit.ma.jpg b/app/static/img/instructors/ilma@algorit.ma.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/ilma@algorit.ma.jpg differ diff --git a/app/static/img/instructors/inayatus@algorit.ma.JPG b/app/static/img/instructors/inayatus@algorit.ma.JPG new file mode 100644 index 0000000..93d2703 Binary files /dev/null and b/app/static/img/instructors/inayatus@algorit.ma.JPG differ diff --git a/app/static/img/instructors/nabiilah@algorit.ma.jpg b/app/static/img/instructors/nabiilah@algorit.ma.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/nabiilah@algorit.ma.jpg differ diff --git a/app/static/img/instructors/samuel@algorit.ma.jpg b/app/static/img/instructors/samuel@algorit.ma.jpg new file mode 100644 index 0000000..9a7c725 Binary files /dev/null and b/app/static/img/instructors/samuel@algorit.ma.jpg differ diff --git a/app/static/img/instructors/shelloren@algorit.ma.jpg b/app/static/img/instructors/shelloren@algorit.ma.jpg new file mode 100644 index 0000000..943b81e Binary files /dev/null and b/app/static/img/instructors/shelloren@algorit.ma.jpg differ diff --git a/app/static/img/instructors/sitta@algorit.ma.JPG b/app/static/img/instructors/sitta@algorit.ma.JPG new file mode 100644 index 0000000..2b54fa2 Binary files /dev/null and b/app/static/img/instructors/sitta@algorit.ma.JPG differ diff --git a/app/static/img/instructors/steve@algorit.ma.jpg b/app/static/img/instructors/steve@algorit.ma.jpg new file mode 100644 index 0000000..3a8bab8 Binary files /dev/null and b/app/static/img/instructors/steve@algorit.ma.jpg differ diff --git a/app/static/img/instructors/tanesya@algorit.ma.jpg b/app/static/img/instructors/tanesya@algorit.ma.jpg new file mode 100644 index 0000000..a9d2f70 Binary files /dev/null and b/app/static/img/instructors/tanesya@algorit.ma.jpg differ diff --git a/app/static/img/instructors/tiara@algorit.ma.JPG b/app/static/img/instructors/tiara@algorit.ma.JPG new file mode 100644 index 0000000..e49c35d Binary files /dev/null and b/app/static/img/instructors/tiara@algorit.ma.JPG differ diff --git a/app/static/img/instructors/viska@algorit.ma.jpg b/app/static/img/instructors/viska@algorit.ma.jpg new file mode 100644 index 0000000..46fb6f0 Binary files /dev/null and b/app/static/img/instructors/viska@algorit.ma.jpg differ diff --git a/app/static/img/instructors/wulan@algorit.ma.JPG b/app/static/img/instructors/wulan@algorit.ma.JPG new file mode 100644 index 0000000..b76c551 Binary files /dev/null and b/app/static/img/instructors/wulan@algorit.ma.JPG differ diff --git a/app/static/img/logo.png b/app/static/img/logo.png new file mode 100644 index 0000000..6db2093 Binary files /dev/null and b/app/static/img/logo.png differ diff --git a/app/static/img/logo_inverted.png b/app/static/img/logo_inverted.png new file mode 100644 index 0000000..47c44c6 Binary files /dev/null and b/app/static/img/logo_inverted.png differ diff --git a/app/templates/base.html b/app/templates/base.html index a0d38a1..e84f0cf 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,5 +1,6 @@ +
@@ -9,53 +10,61 @@ +
+ {% endfor %}
+ | + Name + | ++ Total Training Hours + | ++ Total Students Reached + | + + + {% for person in stats['topten'].items %} +
|---|---|---|
| + {{ person.name }} + | ++ {{ person.total_hours }} Hours + | ++ {{ person.total_students }} Students + | +
Registered as an instructor on Pedagogy?
Log in and view detailed performance statistics: