217 lines
5.0 KiB
Python
217 lines
5.0 KiB
Python
#!/usr/bin/env python
|
|
# encoding: utf-8
|
|
|
|
from dash.dependencies import Input, Output, State
|
|
from dash.exceptions import PreventUpdate
|
|
import plotly.graph_objects as go
|
|
import dash_table
|
|
import json
|
|
import pandas as pd
|
|
import numpy as np
|
|
|
|
from recopytex.dashboard.app import app
|
|
from recopytex.dashboard.common.formating import highlight_scores
|
|
|
|
from .models import (
|
|
get_tribes,
|
|
get_exams,
|
|
get_unstack_scores,
|
|
get_students_from_exam,
|
|
get_score_colors,
|
|
get_level_color_bar,
|
|
score_to_final_mark,
|
|
stack_scores,
|
|
pivot_score_on,
|
|
)
|
|
|
|
|
|
@app.callback(
|
|
[
|
|
Output("exam_select", "options"),
|
|
Output("exam_select", "value"),
|
|
],
|
|
[Input("tribe", "value")],
|
|
)
|
|
def update_exams_choices(tribe):
|
|
if not tribe:
|
|
raise PreventUpdate
|
|
exams = get_exams(tribe)
|
|
exams.reset_index(inplace=True)
|
|
if not exams.empty:
|
|
return [
|
|
{"label": e["name"], "value": e.to_json()} for i, e in exams.iterrows()
|
|
], exams.loc[0].to_json()
|
|
return [], None
|
|
|
|
|
|
@app.callback(
|
|
[
|
|
Output("scores_table", "columns"),
|
|
Output("scores_table", "data"),
|
|
Output("scores_table", "style_data_conditional"),
|
|
Output("scores_table", "fixed_columns"),
|
|
],
|
|
[
|
|
Input("exam_select", "value"),
|
|
],
|
|
)
|
|
def update_scores_store(exam):
|
|
if not exam:
|
|
return [[], [], [], {}]
|
|
exam = pd.DataFrame.from_dict([json.loads(exam)])
|
|
scores = get_unstack_scores(exam)
|
|
fixed_columns = [
|
|
"exercise",
|
|
"question",
|
|
"competence",
|
|
"theme",
|
|
"comment",
|
|
"score_rate",
|
|
"is_leveled",
|
|
]
|
|
|
|
students = list(get_students_from_exam(exam))
|
|
columns = fixed_columns + students
|
|
|
|
score_color = get_score_colors()
|
|
|
|
return [
|
|
[{"id": c, "name": c} for c in columns],
|
|
scores.to_dict("records"),
|
|
highlight_scores(students, score_color),
|
|
{"headers": True, "data": len(fixed_columns)},
|
|
]
|
|
|
|
|
|
@app.callback(
|
|
[
|
|
Output("final_score_table", "data"),
|
|
],
|
|
[
|
|
Input("scores_table", "data"),
|
|
],
|
|
)
|
|
def update_finale_score_table(scores):
|
|
scores_df = pd.DataFrame.from_records(scores)
|
|
stacked_scores = stack_scores(scores_df)
|
|
return score_to_final_mark(stacked_scores)
|
|
|
|
|
|
@app.callback(
|
|
[
|
|
Output("score_statistics_table", "columns"),
|
|
Output("score_statistics_table", "data"),
|
|
],
|
|
[
|
|
Input("final_score_table", "data"),
|
|
],
|
|
)
|
|
def update_statictics_table(finale_score):
|
|
df = pd.DataFrame.from_records(finale_score)
|
|
statistics = df["mark"].describe().to_frame().T
|
|
return [
|
|
[{"id": c, "name": c} for c in statistics.columns],
|
|
statistics.to_dict("records"),
|
|
]
|
|
|
|
|
|
@app.callback(
|
|
[
|
|
Output("fig_exam_histo", "figure"),
|
|
],
|
|
[
|
|
Input("final_score_table", "data"),
|
|
],
|
|
)
|
|
def update_exam_histo(finale_scores):
|
|
scores = pd.DataFrame.from_records(finale_scores)
|
|
|
|
if scores.empty:
|
|
return [go.Figure(data=[go.Scatter(x=[], y=[])])]
|
|
|
|
ranges = np.linspace(
|
|
-0.5,
|
|
scores["score_rate"].max(),
|
|
int(scores["score_rate"].max() * 2 + 2),
|
|
)
|
|
|
|
bins = pd.cut(scores["mark"], ranges)
|
|
scores["Bin"] = bins
|
|
grouped = (
|
|
scores.reset_index()
|
|
.groupby("Bin")
|
|
.agg({"score_rate": "count", "student_name": lambda x: "\n".join(x)})
|
|
)
|
|
grouped.index = grouped.index.map(lambda i: i.right)
|
|
fig = go.Figure()
|
|
fig.add_bar(
|
|
x=grouped.index,
|
|
y=grouped["score_rate"],
|
|
text=grouped["student_name"],
|
|
textposition="auto",
|
|
hovertemplate="",
|
|
marker_color="#4E89DE",
|
|
)
|
|
fig.update_layout(
|
|
height=300,
|
|
margin=dict(l=5, r=5, b=5, t=5),
|
|
)
|
|
return [fig]
|
|
|
|
|
|
@app.callback(
|
|
[
|
|
Output("fig_questions_bar", "figure"),
|
|
],
|
|
[
|
|
Input("scores_table", "data"),
|
|
],
|
|
)
|
|
def update_questions_bar(finale_scores):
|
|
scores = pd.DataFrame.from_records(finale_scores)
|
|
scores = stack_scores(scores)
|
|
|
|
if scores.empty:
|
|
return [go.Figure(data=[go.Scatter(x=[], y=[])])]
|
|
|
|
pt = pivot_score_on(scores, ["exercise", "question", "comment"], "score")
|
|
|
|
# separation between exercises
|
|
for i in {i for i in pt.index.get_level_values(0)}:
|
|
pt.loc[(str(i), "", ""), :] = ""
|
|
pt.sort_index(inplace=True)
|
|
|
|
# Bar label
|
|
index = (
|
|
pt.index.get_level_values(0).map(str)
|
|
+ ":"
|
|
+ pt.index.get_level_values(1).map(str)
|
|
+ " "
|
|
+ pt.index.get_level_values(2).map(str)
|
|
)
|
|
|
|
fig = go.Figure()
|
|
|
|
bars = get_level_color_bar()
|
|
|
|
for b in bars:
|
|
try:
|
|
fig.add_bar(
|
|
x=index, y=pt[b["score"]], name=b["name"], marker_color=b["color"]
|
|
)
|
|
except KeyError:
|
|
pass
|
|
fig.update_layout(barmode="relative")
|
|
fig.update_layout(
|
|
height=500,
|
|
margin=dict(l=5, r=5, b=5, t=5),
|
|
legend=dict(
|
|
orientation="h",
|
|
yanchor="bottom",
|
|
y=1.02,
|
|
xanchor="right",
|
|
x=1
|
|
)
|
|
)
|
|
return [fig]
|