Compare commits
No commits in common. "97b97af2de9c3eac03e2ac802ed0330e668c6768" and "8cdeecfc5372a7d25b747bd090aa5e3a4a5fa242" have entirely different histories.
97b97af2de
...
8cdeecfc53
@ -72,14 +72,6 @@ layout = html.Div(
|
|||||||
],
|
],
|
||||||
id="fig_exam_histo_container",
|
id="fig_exam_histo_container",
|
||||||
),
|
),
|
||||||
html.Div(
|
|
||||||
children=[
|
|
||||||
dcc.Graph(
|
|
||||||
id="fig_questions_bar",
|
|
||||||
)
|
|
||||||
],
|
|
||||||
id="fig_questions_bar_container",
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
id="analysis",
|
id="analysis",
|
||||||
),
|
),
|
||||||
|
@ -18,10 +18,7 @@ from .models import (
|
|||||||
get_unstack_scores,
|
get_unstack_scores,
|
||||||
get_students_from_exam,
|
get_students_from_exam,
|
||||||
get_score_colors,
|
get_score_colors,
|
||||||
get_level_color_bar,
|
|
||||||
score_to_final_mark,
|
score_to_final_mark,
|
||||||
stack_scores,
|
|
||||||
pivot_score_on,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -93,8 +90,8 @@ def update_scores_store(exam):
|
|||||||
)
|
)
|
||||||
def update_finale_score_table(scores):
|
def update_finale_score_table(scores):
|
||||||
scores_df = pd.DataFrame.from_records(scores)
|
scores_df = pd.DataFrame.from_records(scores)
|
||||||
stacked_scores = stack_scores(scores_df)
|
# print(scores_df)
|
||||||
return score_to_final_mark(stacked_scores)
|
return score_to_final_mark(scores_df)
|
||||||
|
|
||||||
|
|
||||||
@app.callback(
|
@app.callback(
|
||||||
@ -109,6 +106,7 @@ def update_finale_score_table(scores):
|
|||||||
def update_statictics_table(finale_score):
|
def update_statictics_table(finale_score):
|
||||||
df = pd.DataFrame.from_records(finale_score)
|
df = pd.DataFrame.from_records(finale_score)
|
||||||
statistics = df["mark"].describe().to_frame().T
|
statistics = df["mark"].describe().to_frame().T
|
||||||
|
print(statistics)
|
||||||
return [
|
return [
|
||||||
[{"id": c, "name": c} for c in statistics.columns],
|
[{"id": c, "name": c} for c in statistics.columns],
|
||||||
statistics.to_dict("records"),
|
statistics.to_dict("records"),
|
||||||
@ -157,53 +155,3 @@ def update_exam_histo(finale_scores):
|
|||||||
margin=dict(l=5, r=5, b=5, t=5),
|
margin=dict(l=5, r=5, b=5, t=5),
|
||||||
)
|
)
|
||||||
return [fig]
|
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),
|
|
||||||
)
|
|
||||||
return [fig]
|
|
||||||
|
@ -70,13 +70,6 @@ def get_score_colors():
|
|||||||
return score_color
|
return score_color
|
||||||
|
|
||||||
|
|
||||||
def get_level_color_bar():
|
|
||||||
return [
|
|
||||||
{"score": s["value"], "name": s["comment"], "color": s["color"]}
|
|
||||||
for s in SCORES_CONFIG.values()
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
is_none_score = lambda x: on_column.is_none_score(x, SCORES_CONFIG)
|
is_none_score = lambda x: on_column.is_none_score(x, SCORES_CONFIG)
|
||||||
score_to_numeric_score = lambda x: on_column.score_to_numeric_score(x, SCORES_CONFIG)
|
score_to_numeric_score = lambda x: on_column.score_to_numeric_score(x, SCORES_CONFIG)
|
||||||
score_to_mark = lambda x: on_column.score_to_mark(
|
score_to_mark = lambda x: on_column.score_to_mark(
|
||||||
@ -87,7 +80,8 @@ score_to_mark = lambda x: on_column.score_to_mark(
|
|||||||
def score_to_final_mark(scores):
|
def score_to_final_mark(scores):
|
||||||
""" Compute marks then reduce to final mark per student """
|
""" Compute marks then reduce to final mark per student """
|
||||||
|
|
||||||
filtered_scores = scores[~scores.apply(is_none_score, axis=1)]
|
melted_scores = stack_scores(scores)
|
||||||
|
filtered_scores = melted_scores[~melted_scores.apply(is_none_score, axis=1)]
|
||||||
filtered_scores = filtered_scores.assign(
|
filtered_scores = filtered_scores.assign(
|
||||||
score=filtered_scores.apply(score_to_numeric_score, axis=1)
|
score=filtered_scores.apply(score_to_numeric_score, axis=1)
|
||||||
)
|
)
|
||||||
@ -99,20 +93,3 @@ def score_to_final_mark(scores):
|
|||||||
].sum()
|
].sum()
|
||||||
return [final_score.reset_index().to_dict("records")]
|
return [final_score.reset_index().to_dict("records")]
|
||||||
|
|
||||||
|
|
||||||
def pivot_score_on(scores, index, columns, aggfunc="size"):
|
|
||||||
"""Pivot scores on index, columns with aggfunc
|
|
||||||
|
|
||||||
It assumes thant scores are levels
|
|
||||||
|
|
||||||
"""
|
|
||||||
filtered_scores = scores[~scores.apply(is_none_score, axis=1)]
|
|
||||||
pt = pd.pivot_table(
|
|
||||||
scores,
|
|
||||||
index=index,
|
|
||||||
columns=columns,
|
|
||||||
aggfunc=aggfunc,
|
|
||||||
fill_value=0,
|
|
||||||
)
|
|
||||||
return pt
|
|
||||||
|
|
||||||
|
@ -26,37 +26,30 @@ scores: #
|
|||||||
value: 0
|
value: 0
|
||||||
numeric_value: 0
|
numeric_value: 0
|
||||||
color: "#E7472B"
|
color: "#E7472B"
|
||||||
comment: Faux
|
|
||||||
FEW: # Few good things
|
FEW: # Few good things
|
||||||
value: 1
|
value: 1
|
||||||
numeric_value: 1
|
numeric_value: 1
|
||||||
color: "#FF712B"
|
color: "#FF712B"
|
||||||
comment: Peu juste
|
|
||||||
NEARLY: # Nearly good but things are missing
|
NEARLY: # Nearly good but things are missing
|
||||||
value: 2
|
value: 2
|
||||||
numeric_value: 2
|
numeric_value: 2
|
||||||
color: "#F2EC4C"
|
color: "#F2EC4C"
|
||||||
comment: Presque juste
|
|
||||||
GOOD: # Everything is good
|
GOOD: # Everything is good
|
||||||
value: 3
|
value: 3
|
||||||
numeric_value: 3
|
numeric_value: 3
|
||||||
color: "#68D42F"
|
color: "#68D42F"
|
||||||
comment: Juste
|
|
||||||
NOTFILLED: # The item is not scored yet
|
NOTFILLED: # The item is not scored yet
|
||||||
value: ""
|
value: ""
|
||||||
numeric_value: None
|
numeric_value: None
|
||||||
color: white
|
color: white
|
||||||
comment: En attente
|
|
||||||
NOANSWER: # Student gives no answer (count as 0)
|
NOANSWER: # Student gives no answer (count as 0)
|
||||||
value: "."
|
value: "."
|
||||||
numeric_value: 0
|
numeric_value: 0
|
||||||
color: black
|
color: black
|
||||||
comment: Pas de réponse
|
|
||||||
ABS: # Student has absent (this score won't be impact the final mark)
|
ABS: # Student has absent (this score won't be impact the final mark)
|
||||||
value: a
|
value: a
|
||||||
numeric_value: None
|
numeric_value: None
|
||||||
color: lightgray
|
color: lightgray
|
||||||
comment: Non noté
|
|
||||||
|
|
||||||
csv_fields: # dataframe_field: csv_field
|
csv_fields: # dataframe_field: csv_field
|
||||||
term: Trimestre
|
term: Trimestre
|
||||||
|
@ -31,11 +31,10 @@ class CSVLoader(Loader):
|
|||||||
:example:
|
:example:
|
||||||
>>> loader = CSVLoader()
|
>>> loader = CSVLoader()
|
||||||
>>> loader.get_config()
|
>>> loader.get_config()
|
||||||
{'source': './', 'competences': {'Chercher': {'name': 'Chercher', 'abrv': 'Cher'}, 'Représenter': {'name': 'Représenter', 'abrv': 'Rep'}, 'Modéliser': {'name': 'Modéliser', 'abrv': 'Mod'}, 'Raisonner': {'name': 'Raisonner', 'abrv': 'Rai'}, 'Calculer': {'name': 'Calculer', 'abrv': 'Cal'}, 'Communiquer': {'name': 'Communiquer', 'abrv': 'Com'}}, 'scores': {'BAD': {'value': 0, 'numeric_value': 0, 'color': '#E7472B', 'comment': 'Faux'}, 'FEW': {'value': 1, 'numeric_value': 1, 'color': '#FF712B', 'comment': 'Peu juste'}, 'NEARLY': {'value': 2, 'numeric_value': 2, 'color': '#F2EC4C', 'comment': 'Presque juste'}, 'GOOD': {'value': 3, 'numeric_value': 3, 'color': '#68D42F', 'comment': 'Juste'}, 'NOTFILLED': {'value': '', 'numeric_value': 'None', 'color': 'white', 'comment': 'En attente'}, 'NOANSWER': {'value': '.', 'numeric_value': 0, 'color': 'black', 'comment': 'Pas de réponse'}, 'ABS': {'value': 'a', 'numeric_value': 'None', 'color': 'lightgray', 'comment': 'Non noté'}}, 'csv_fields': {'term': 'Trimestre', 'exam': 'Nom', 'date': 'Date', 'exercise': 'Exercice', 'question': 'Question', 'competence': 'Competence', 'theme': 'Domaine', 'comment': 'Commentaire', 'score_rate': 'Bareme', 'is_leveled': 'Est_nivele'}, 'id_templates': {'exam': '{name}_{tribe}', 'question': '{exam_id}_{exercise}_{question}_{comment}'}}
|
{'source': './', 'competences': {'Chercher': {'name': 'Chercher', 'abrv': 'Cher'}, 'Représenter': {'name': 'Représenter', 'abrv': 'Rep'}, 'Modéliser': {'name': 'Modéliser', 'abrv': 'Mod'}, 'Raisonner': {'name': 'Raisonner', 'abrv': 'Rai'}, 'Calculer': {'name': 'Calculer', 'abrv': 'Cal'}, 'Communiquer': {'name': 'Communiquer', 'abrv': 'Com'}}, 'scores': {'BAD': {'value': 0, 'numeric_value': 0, 'color': '#E7472B'}, 'FEW': {'value': 1, 'numeric_value': 1, 'color': '#FF712B'}, 'NEARLY': {'value': 2, 'numeric_value': 2, 'color': '#F2EC4C'}, 'GOOD': {'value': 3, 'numeric_value': 3, 'color': '#68D42F'}, 'NOTFILLED': {'value': '', 'numeric_value': 'None', 'color': 'white'}, 'NOANSWER': {'value': '.', 'numeric_value': 0, 'color': 'black'}, 'ABS': {'value': 'a', 'numeric_value': 'None', 'color': 'lightgray'}}, 'csv_fields': {'term': 'Trimestre', 'exam': 'Nom', 'date': 'Date', 'exercise': 'Exercice', 'question': 'Question', 'competence': 'Competence', 'theme': 'Domaine', 'comment': 'Commentaire', 'score_rate': 'Bareme', 'is_leveled': 'Est_nivele'}, 'id_templates': {'exam': '{name}_{tribe}', 'question': '{exam_id}_{exercise}_{question}_{comment}'}}
|
||||||
|
|
||||||
>>> loader = CSVLoader("./test_config.yml")
|
>>> loader = CSVLoader("./test_config.yml")
|
||||||
>>> loader.get_config()
|
>>> loader.get_config()
|
||||||
{'source': './example', 'competences': {'Chercher': {'name': 'Chercher', 'abrv': 'Cher'}, 'Représenter': {'name': 'Représenter', 'abrv': 'Rep'}, 'Modéliser': {'name': 'Modéliser', 'abrv': 'Mod'}, 'Raisonner': {'name': 'Raisonner', 'abrv': 'Rai'}, 'Calculer': {'name': 'Calculer', 'abrv': 'Cal'}, 'Communiquer': {'name': 'Communiquer', 'abrv': 'Com'}}, 'scores': {'BAD': {'value': 0, 'numeric_value': 0, 'color': '#E7472B', 'comment': 'Faux'}, 'FEW': {'value': 1, 'numeric_value': 1, 'color': '#FF712B', 'comment': 'Peu juste'}, 'NEARLY': {'value': 2, 'numeric_value': 2, 'color': '#F2EC4C', 'comment': 'Presque juste'}, 'GOOD': {'value': 3, 'numeric_value': 3, 'color': '#68D42F', 'comment': 'Juste'}, 'NOTFILLED': {'value': '', 'numeric_value': 'None', 'color': 'white', 'comment': 'En attente'}, 'NOANSWER': {'value': '.', 'numeric_value': 0, 'color': 'black', 'comment': 'Pas de réponse'}, 'ABS': {'value': 'a', 'numeric_value': 'None', 'color': 'lightgray', 'comment': 'Non noté'}}, 'csv_fields': {'term': 'Trimestre', 'exam': 'Nom', 'date': 'Date', 'exercise': 'Exercice', 'question': 'Question', 'competence': 'Competence', 'theme': 'Domaine', 'comment': 'Commentaire', 'score_rate': 'Bareme', 'is_leveled': 'Est_nivele'}, 'id_templates': {'exam': '{name}_{tribe}', 'question': '{exam_id}_{exercise}_{question}_{comment}'}, 'output': './output', 'templates': 'templates/', 'tribes': {'Tribe1': {'name': 'Tribe1', 'type': 'Type1', 'students': 'tribe1.csv'}, 'Tribe2': {'name': 'Tribe2', 'students': 'tribe2.csv'}}}
|
{'source': './example', 'competences': {'Chercher': {'name': 'Chercher', 'abrv': 'Cher'}, 'Représenter': {'name': 'Représenter', 'abrv': 'Rep'}, 'Modéliser': {'name': 'Modéliser', 'abrv': 'Mod'}, 'Raisonner': {'name': 'Raisonner', 'abrv': 'Rai'}, 'Calculer': {'name': 'Calculer', 'abrv': 'Cal'}, 'Communiquer': {'name': 'Communiquer', 'abrv': 'Com'}}, 'scores': {'BAD': {'value': 0, 'numeric_value': 0, 'color': '#E7472B'}, 'FEW': {'value': 1, 'numeric_value': 1, 'color': '#FF712B'}, 'NEARLY': {'value': 2, 'numeric_value': 2, 'color': '#F2EC4C'}, 'GOOD': {'value': 3, 'numeric_value': 3, 'color': '#68D42F'}, 'NOTFILLED': {'value': '', 'numeric_value': 'None', 'color': 'white'}, 'NOANSWER': {'value': '.', 'numeric_value': 0, 'color': 'black'}, 'ABS': {'value': 'a', 'numeric_value': 'None', 'color': 'lightgray'}}, 'csv_fields': {'term': 'Trimestre', 'exam': 'Nom', 'date': 'Date', 'exercise': 'Exercice', 'question': 'Question', 'competence': 'Competence', 'theme': 'Domaine', 'comment': 'Commentaire', 'score_rate': 'Bareme', 'is_leveled': 'Est_nivele'}, 'id_templates': {'exam': '{name}_{tribe}', 'question': '{exam_id}_{exercise}_{question}_{comment}'}, 'output': './output', 'templates': 'templates/', 'tribes': {'Tribe1': {'name': 'Tribe1', 'type': 'Type1', 'students': 'tribe1.csv'}, 'Tribe2': {'name': 'Tribe2', 'students': 'tribe2.csv'}}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CONFIG = DEFAULT_CONFIG
|
CONFIG = DEFAULT_CONFIG
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
|
||||||
from math import ceil
|
from math import ceil
|
||||||
import pandas as pd
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
def is_none_score(x, score_config):
|
def is_none_score(x, score_config):
|
||||||
@ -40,7 +40,7 @@ def is_none_score(x, score_config):
|
|||||||
for v in score_config.values()
|
for v in score_config.values()
|
||||||
if str(v["numeric_value"]).lower() == "none"
|
if str(v["numeric_value"]).lower() == "none"
|
||||||
]
|
]
|
||||||
return x["score"] in none_values or pd.isnull(x["score"])
|
return x["score"] in none_values or x["score"] is None or np.isnan(x["score"])
|
||||||
|
|
||||||
|
|
||||||
def score_to_numeric_score(x, score_config):
|
def score_to_numeric_score(x, score_config):
|
||||||
|
Loading…
Reference in New Issue
Block a user