Compare commits

10 Commits

9 changed files with 209 additions and 46 deletions

View File

@@ -1,5 +1,5 @@
Trimestre,Nom,Date,Exercice,Question,Competence,Domaine,Commentaire,Bareme,Est_nivele,Star Tice,Umberto Dingate,Starlin Crangle,Humbert Bourcq,Gabriella Handyside,Stewart Eaves,Erick Going,Ase Praton,Rollins Planks,Dunstan Sarjant,Stacy Guiton,Ange Stanes,Amabelle Elleton,Darn Broomhall,Dyan Chatto,Keane Rennebach,Nari Paulton,Brandy Wase,Jaclyn Firidolfi,Violette Lockney Trimestre,Nom,Date,Exercice,Question,Competence,Domaine,Commentaire,Bareme,Est_nivele,Star Tice,Umberto Dingate,Starlin Crangle,Humbert Bourcq,Gabriella Handyside,Stewart Eaves,Erick Going,Ase Praton,Rollins Planks,Dunstan Sarjant,Stacy Guiton,Ange Stanes,Amabelle Elleton,Darn Broomhall,Dyan Chatto,Keane Rennebach,Nari Paulton,Brandy Wase,Jaclyn Firidolfi,Violette Lockney
1,DS,12/01/2021,Exercice 1,1,Calculer,Plop,Coucou,1,1,,,1.0,0,1.0,2.0,3.0,0.0,3.0,3.0,2.0,,1.0,,,,,,, 1,DS,12/01/2021,Exercice 1,1,Calculer,Plop,Coucou,1,1,,,1,0,1,2,3,0,3,3,2,,1,,,,,,,
1,DS,12/01/2021,Exercice 1,2,Calculer,C'est trop chouette!,Coucou,1,1,,,1.0,2,,,3.0,3.0,,,,,2.0,,,,,,, 1,DS,12/01/2021,Exercice 1,2,Calculer,C'est trop chouette!,Coucou,1,1,,,1,2,,,3,3,,,,,2,,,,,,,
1,DS,12/01/2021,Exercice 1,3,Calculer,Null,Coucou,1,1,,,,3,2.0,,,,,,,,3.0,,,,,,, 1,DS,12/01/2021,Exercice 1,3,Calculer,Null,Coucou,1,1,,,,3,2,,,,,,,,3,,,,,,,
1,DS,12/01/2021,Exercice 1,3,Calculer,Nié,DChic,1,1,,,,2,,,,,,,,,,,,,,,, 1,DS,12/01/2021,Exercice 1,3,Calculer,Nié,DChic,1,1,,,,2,.,,,,,,,,,,,,,,,
1 Trimestre Nom Date Exercice Question Competence Domaine Commentaire Bareme Est_nivele Star Tice Umberto Dingate Starlin Crangle Humbert Bourcq Gabriella Handyside Stewart Eaves Erick Going Ase Praton Rollins Planks Dunstan Sarjant Stacy Guiton Ange Stanes Amabelle Elleton Darn Broomhall Dyan Chatto Keane Rennebach Nari Paulton Brandy Wase Jaclyn Firidolfi Violette Lockney
2 1 DS 12/01/2021 Exercice 1 1 Calculer Plop Coucou 1 1 1.0 1 0 1.0 1 2.0 2 3.0 3 0.0 0 3.0 3 3.0 3 2.0 2 1.0 1
3 1 DS 12/01/2021 Exercice 1 2 Calculer C'est trop chouette! Coucou 1 1 1.0 1 2 3.0 3 3.0 3 2.0 2
4 1 DS 12/01/2021 Exercice 1 3 Calculer Null Coucou 1 1 3 2.0 2 3.0 3
5 1 DS 12/01/2021 Exercice 1 3 Calculer Nié DChic 1 1 2 .

View File

@@ -68,10 +68,20 @@ layout = html.Div(
children=[ children=[
dcc.Graph( dcc.Graph(
id="fig_exam_histo", id="fig_exam_histo",
config={"displayModeBar": False},
) )
], ],
id="fig_exam_histo_container", id="fig_exam_histo_container",
), ),
html.Div(
children=[
dcc.Graph(
id="fig_questions_bar",
config={"displayModeBar": False},
)
],
id="fig_questions_bar_container",
),
], ],
id="analysis", id="analysis",
), ),

View File

@@ -18,7 +18,10 @@ 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,
) )
@@ -90,8 +93,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)
# print(scores_df) stacked_scores = stack_scores(scores_df)
return score_to_final_mark(scores_df) return score_to_final_mark(stacked_scores)
@app.callback( @app.callback(
@@ -106,7 +109,6 @@ 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"),
@@ -155,3 +157,60 @@ 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),
legend=dict(
orientation="h",
yanchor="bottom",
y=1.02,
xanchor="right",
x=1
)
)
return [fig]

View File

@@ -70,18 +70,33 @@ def get_score_colors():
return score_color return score_color
def get_level_color_bar():
return [
{"score": str(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)
format_score = lambda x: on_column.format_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(
x, max([v["value"] for v in SCORES_CONFIG.values() if isinstance(v["value"], int)]) x, max([v["value"] for v in SCORES_CONFIG.values() if isinstance(v["value"], int)])
) )
def filter_clean_score(scores):
filtered_scores = scores[~scores.apply(is_none_score, axis=1)]
filtered_scores = filtered_scores.assign(
score=filtered_scores.apply(format_score, axis=1)
)
return filtered_scores
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 """
melted_scores = stack_scores(scores) filtered_scores = filter_clean_score(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)
) )
@@ -93,3 +108,21 @@ 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 = filter_clean_score(scores)
filtered_scores["score"] = filtered_scores["score"].astype(str)
pt = pd.pivot_table(
filtered_scores,
index=index,
columns=columns,
aggfunc=aggfunc,
fill_value=0,
)
return pt

View File

@@ -6,6 +6,7 @@ from dash.dependencies import Input, Output
from .app import app from .app import app
from .pages.home import app as home from .pages.home import app as home
from .pages.exams_scores import app as exams_scores from .pages.exams_scores import app as exams_scores
import dash_html_components as html
@app.callback(Output("page-content", "children"), [Input("url", "pathname")]) @app.callback(Output("page-content", "children"), [Input("url", "pathname")])

View File

@@ -26,30 +26,37 @@ 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

View File

@@ -31,10 +31,11 @@ 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'}, '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}'}} {'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}'}}
>>> 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'}, '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'}}} {'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'}}}
""" """
CONFIG = DEFAULT_CONFIG CONFIG = DEFAULT_CONFIG

View File

@@ -2,7 +2,7 @@
# encoding: utf-8 # encoding: utf-8
from math import ceil from math import ceil
import numpy as np import pandas as pd
def is_none_score(x, score_config): def is_none_score(x, score_config):
@@ -40,7 +40,58 @@ 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 x["score"] is None or np.isnan(x["score"]) return x["score"] in none_values or pd.isnull(x["score"])
def format_score(x, score_config):
"""Make sure that score have the appropriate format
>>> import pandas as pd
>>> d = {"Eleve":["E1"]*6,
... "score_rate": [1]*6,
... "is_leveled":[0]+[1]*5,
... "score":[0.33, ".", "a", 1, 2, 3],
... }
>>> score_config = {
... 'BAD': {'value': 0, 'numeric_value': 0},
... 'FEW': {'value': 1, 'numeric_value': 1},
... 'NEARLY': {'value': 2, 'numeric_value': 2},
... 'GOOD': {'value': 3, 'numeric_value': 3},
... 'NOTFILLED': {'value': '', 'numeric_value': 'None'},
... 'NOANSWER': {'value': '.', 'numeric_value': 0},
... 'ABS': {'value': 'a', 'numeric_value': 'None'}
... }
>>> df = pd.DataFrame(d)
>>> df.apply(lambda x:format_score(x, score_config), axis=1)
0 0.33
1 .
2 a
3 1
4 2
5 3
dtype: object
>>> format_score({"score": "1.0", "is_leveled": 1}, score_config)
1
>>> format_score({"score": "3.0", "is_leveled": 1}, score_config)
3
>>> format_score({"score": 4, "is_leveled": 1}, score_config)
Traceback (most recent call last):
...
ValueError: 4 (<class 'int'>) can't be a score
"""
if not x["is_leveled"]:
return float(x["score"])
try:
score = int(float(x["score"]))
except ValueError:
score = str(x["score"])
if score in [v["value"] for v in score_config.values()]:
return score
raise ValueError(f"{x['score']} ({type(x['score'])}) can't be a score")
def score_to_numeric_score(x, score_config): def score_to_numeric_score(x, score_config):
@@ -81,7 +132,7 @@ def score_to_numeric_score(x, score_config):
def score_to_mark(x, score_max, rounding=lambda x: round(x, 2)): def score_to_mark(x, score_max, rounding=lambda x: round(x, 2)):
"""Compute the mark from the score """Compute the mark from "score" which have to be filtered and in numeric form
if the item is leveled then the score is multiply by the score_rate if the item is leveled then the score is multiply by the score_rate
otherwise it copies the score otherwise it copies the score
@@ -92,39 +143,38 @@ def score_to_mark(x, score_max, rounding=lambda x: round(x, 2)):
:return: the mark :return: the mark
>>> import pandas as pd >>> import pandas as pd
>>> d = {"Eleve":["E1"]*6 + ["E2"]*6, >>> d = {"Eleve":["E1"]*7,
... "score_rate":[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2, ... "score_rate": [1]*7,
... "is_leveled":[0]*4+[1]*2 + [0]*4+[1]*2, ... "is_leveled":[0]+[1]*6,
... "score":[1, 0.33, 2, 1.5, 1, 3, 0.666, 1, 1.5, 1.2, 2, 3], ... "score":[0.33, "", ".", "a", 1, 2, 3],
... }
>>> score_config = {
... 'BAD': {'value': 0, 'numeric_value': 0},
... 'FEW': {'value': 1, 'numeric_value': 1},
... 'NEARLY': {'value': 2, 'numeric_value': 2},
... 'GOOD': {'value': 3, 'numeric_value': 3},
... 'NOTFILLED': {'value': '', 'numeric_value': 'None'},
... 'NOANSWER': {'value': '.', 'numeric_value': 0},
... 'ABS': {'value': 'a', 'numeric_value': 'None'}
... } ... }
>>> df = pd.DataFrame(d) >>> df = pd.DataFrame(d)
>>> df.loc[0] >>> df = df[~df.apply(lambda x:is_none_score(x, score_config), axis=1)]
Eleve E1 >>> df["score"] = df.apply(lambda x:score_to_numeric_score(x, score_config), axis=1)
score_rate 1 >>> df.apply(lambda x:score_to_mark(x, 3), axis=1)
is_leveled 0 0 0.33
score 1.0 2 0.00
Name: 0, dtype: object 4 0.33
>>> score_to_mark(df.loc[0], 3) 5 0.67
1.0 6 1.00
>>> df.loc[10] dtype: float64
Eleve E2
score_rate 2
is_leveled 1
score 2.0
Name: 10, dtype: object
>>> score_to_mark(df.loc[10], 3)
1.33
>>> from .on_value import round_half_point >>> from .on_value import round_half_point
>>> score_to_mark(df.loc[10], 3, round_half_point) >>> df.apply(lambda x:score_to_mark(x, 3, round_half_point), axis=1)
1.5 0 0.5
>>> df.loc[1] 2 0.0
Eleve E1 4 0.5
score_rate 1 5 0.5
is_leveled 0 6 1.0
score 0.33 dtype: float64
Name: 1, dtype: object
>>> score_to_mark(df.loc[1], 3)
0.33
""" """
if x["is_leveled"]: if x["is_leveled"]:
if x["score"] not in list(range(score_max + 1)): if x["score"] not in list(range(score_max + 1)):

View File

@@ -2,8 +2,7 @@
# encoding: utf-8 # encoding: utf-8
import click import click
from recopytex.dashboard.app import app as dash from recopytex.dashboard.index import app as dash
@click.group() @click.group()
def cli(): def cli():
@@ -14,3 +13,6 @@ def cli():
@click.option("--debug", default=0, help="Debug mode for dash") @click.option("--debug", default=0, help="Debug mode for dash")
def dashboard(debug): def dashboard(debug):
dash.run_server(debug=bool(debug)) dash.run_server(debug=bool(debug))
if __name__ == "__main__":
cli()