Compare commits
	
		
			58 Commits
		
	
	
		
			master
			...
			ff94470fb4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ff94470fb4 | |||
| d322452a6e | |||
| e1d3940e9d | |||
| 7dba11996a | |||
| 3250a600c9 | |||
| 589d63ff29 | |||
| 429fed6a1e | |||
| 1255bf4b9e | |||
| 1fe7665753 | |||
| e08e4a32a8 | |||
| b737612adb | |||
| 9c19e2ac56 | |||
| eb60734c26 | |||
| 329bcc460c | |||
| 95fc842c1d | |||
| e0ca1a458b | |||
| eb1abbe868 | |||
| 412e624791 | |||
| e8bf0b3f0a | |||
| c057fa11e7 | |||
| e15119605f | |||
| 494567cdb5 | |||
| 84fcee625d | |||
| f62c898162 | |||
| 7955b989b4 | |||
| 4f14e3518c | |||
| 4bf8f4003e | |||
| a14d47b15c | |||
| 09ac9f01f8 | |||
| 0a5a931d01 | |||
| 21397272c9 | |||
| 894ebc4ec8 | |||
| f6bfac4144 | |||
| cfd5928853 | |||
| 8fcad94df4 | |||
| 27d7c45980 | |||
| 159e7a9f2e | |||
| 72afb26e2a | |||
| 6eb918e0f5 | |||
| 56a669b2be | |||
| a5f22fc8cd | |||
| 5177df06d7 | |||
| d78fcbc281 | |||
| 98fa768541 | |||
| 00c2681823 | |||
| 52f2f3f4cf | |||
| 4ea7f8db14 | |||
| 04a2506d86 | |||
| 77c358b0c1 | |||
| 1886deb430 | |||
| 5e0f2d92ef | |||
| 49cc52f7d1 | |||
| 6d93ef62d7 | |||
| 488df4cb0c | |||
| 9136f359e0 | |||
| 1dfee17990 | |||
| 400fb0a690 | |||
| 04a1ed9378 | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -122,3 +122,7 @@ dmypy.json | ||||
|  | ||||
| # Pyre type checker | ||||
| .pyre/ | ||||
|  | ||||
| # vim | ||||
| .vim | ||||
|  | ||||
|   | ||||
							
								
								
									
										26
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								README.md
									
									
									
									
									
								
							| @@ -6,3 +6,29 @@ Cette fois ci, on utilise: | ||||
| - Des fichiers yaml pour les infos sur les élèves | ||||
| - Des notebooks pour l'analyse | ||||
| - Papermill pour produire les notesbooks à partir de template | ||||
|  | ||||
| ## Les fichiers CSV | ||||
|  | ||||
| les paramètres sont décris dans ./recopytex/config.py | ||||
|  | ||||
| ### Descriptions des questions | ||||
|  | ||||
| - Trimestre | ||||
| - Nom | ||||
| - Date | ||||
| - Exercice | ||||
| - Question | ||||
| - Competence | ||||
| - Domaine | ||||
| - Commentaire | ||||
| - Bareme | ||||
| - Est_nivele | ||||
|  | ||||
|  | ||||
| ### Valeurs pour notes les élèves | ||||
|  | ||||
| - Score: 0, 1, 2, 3 | ||||
| - Pas de réponses: . | ||||
| - Absent: a | ||||
| - Dispensé: (vide) | ||||
|  | ||||
|   | ||||
							
								
								
									
										32
									
								
								example/recoconfig.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								example/recoconfig.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| --- | ||||
| source: ./ | ||||
| output: ./ | ||||
| templates: templates/ | ||||
|  | ||||
| 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 | ||||
|  | ||||
|  | ||||
| tribes: | ||||
|   - name: Tribe1 | ||||
|     type: Type1 | ||||
|     students: tribe1.csv | ||||
|   - name: Tribe2 | ||||
|     students: tribe2.csv | ||||
							
								
								
									
										21
									
								
								example/tribe1.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								example/tribe1.csv
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| Nom,email | ||||
| Star Tice,stice0@jalbum.net | ||||
| Umberto Dingate,udingate1@tumblr.com | ||||
| Starlin Crangle,scrangle2@wufoo.com | ||||
| Humbert Bourcq,hbourcq3@g.co | ||||
| Gabriella Handyside,ghandyside4@patch.com | ||||
| Stewart Eaves,seaves5@ycombinator.com | ||||
| Erick Going,egoing6@va.gov | ||||
| Ase Praton,apraton7@va.gov | ||||
| Rollins Planks,rplanks8@delicious.com | ||||
| Dunstan Sarjant,dsarjant9@naver.com | ||||
| Stacy Guiton,sguitona@themeforest.net | ||||
| Ange Stanes,astanesb@marriott.com | ||||
| Amabelle Elleton,aelletonc@squidoo.com | ||||
| Darn Broomhall,dbroomhalld@cisco.com | ||||
| Dyan Chatto,dchattoe@npr.org | ||||
| Keane Rennebach,krennebachf@dot.gov | ||||
| Nari Paulton,npaultong@gov.uk | ||||
| Brandy Wase,bwaseh@ftc.gov | ||||
| Jaclyn Firidolfi,jfiridolfii@reuters.com | ||||
| Violette Lockney,vlockneyj@chron.com | ||||
| 
 | 
							
								
								
									
										21
									
								
								example/tribe2.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								example/tribe2.csv
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| Nom,email | ||||
| Elle McKintosh,emckintosh0@1und1.de | ||||
| Ty Megany,tmegany1@reuters.com | ||||
| Pippa Borrows,pborrows2@a8.net | ||||
| Sonny Eskrick,seskrick3@123-reg.co.uk | ||||
| Mollee Britch,mbritch4@usda.gov | ||||
| Ingram Plaistowe,iplaistowe5@purevolume.com | ||||
| Fay Vanyard,fvanyard6@sbwire.com | ||||
| Nancy Rase,nrase7@omniture.com | ||||
| Rachael Ruxton,rruxton8@bravesites.com | ||||
| Tallie Rushmer,trushmer9@home.pl | ||||
| Seward MacIlhagga,smacilhaggaa@hatena.ne.jp | ||||
| Lizette Searl,lsearlb@list-manage.com | ||||
| Talya Mannagh,tmannaghc@webnode.com | ||||
| Jordan Witherbed,jwitherbedd@unesco.org | ||||
| Reagan Botcherby,rbotcherbye@scientificamerican.com | ||||
| Libbie Shoulder,lshoulderf@desdev.cn | ||||
| Abner Khomich,akhomichg@youtube.com | ||||
| Zollie Kitman,zkitmanh@forbes.com | ||||
| Fiorenze Durden,fdurdeni@feedburner.com | ||||
| Kevyn Race,kracej@seattletimes.com | ||||
| 
 | 
| @@ -17,7 +17,7 @@ def try_replace(x, old, new): | ||||
|  | ||||
|  | ||||
| def extract_students(df, no_student_columns=NO_ST_COLUMNS.values()): | ||||
|     """ Extract the list of students from df  | ||||
|     """Extract the list of students from df | ||||
|  | ||||
|     :param df: the dataframe | ||||
|     :param no_student_columns: columns that are not students | ||||
| @@ -30,7 +30,7 @@ def extract_students(df, no_student_columns=NO_ST_COLUMNS.values()): | ||||
| def flat_df_students( | ||||
|     df, no_student_columns=NO_ST_COLUMNS.values(), postprocessing=True | ||||
| ): | ||||
|     """ Flat the dataframe by returning a dataframe with on student on each line | ||||
|     """Flat the dataframe by returning a dataframe with on student on each line | ||||
|  | ||||
|     :param df: the dataframe (one row per questions) | ||||
|     :param no_student_columns: columns that are not students | ||||
| @@ -63,7 +63,7 @@ def flat_df_students( | ||||
| def flat_df_for( | ||||
|     df, student, no_student_columns=NO_ST_COLUMNS.values(), postprocessing=True | ||||
| ): | ||||
|     """ Extract the data only for one student | ||||
|     """Extract the data only for one student | ||||
|  | ||||
|     :param df: the dataframe (one row per questions) | ||||
|     :param no_student_columns: columns that are not students | ||||
| @@ -88,7 +88,7 @@ def flat_df_for( | ||||
|  | ||||
|  | ||||
| def postprocess(df): | ||||
|     """ Postprocessing score dataframe  | ||||
|     """Postprocessing score dataframe | ||||
|  | ||||
|     - Replace na with an empty string | ||||
|     - Replace "NOANSWER" with -1 | ||||
|   | ||||
							
								
								
									
										0
									
								
								recopytex/dashboard/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								recopytex/dashboard/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										5
									
								
								recopytex/dashboard/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								recopytex/dashboard/app.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| import dash | ||||
|  | ||||
| app = dash.Dash(__name__, suppress_callback_exceptions=True) | ||||
| # app = dash.Dash(__name__) | ||||
| server = app.server | ||||
							
								
								
									
										66
									
								
								recopytex/dashboard/assets/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								recopytex/dashboard/assets/style.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| body { | ||||
|     margin: 0px; | ||||
|     font-family: 'Source Sans Pro','Roboto','Open Sans','Liberation Sans','DejaVu Sans','Verdana','Helvetica','Arial',sans-serif; | ||||
| } | ||||
|  | ||||
| header { | ||||
|     margin: 0px 0px 20px 0px; | ||||
|     background-color: #333333; | ||||
|     color: #ffffff; | ||||
|     padding: 20px; | ||||
| } | ||||
|  | ||||
| header > h1 { | ||||
|     margin: 0px; | ||||
| } | ||||
|  | ||||
| main { | ||||
|     width: 95vw; | ||||
|     margin: auto; | ||||
| } | ||||
|  | ||||
| section { | ||||
|     margin-top: 20px; | ||||
|     margin-bottom: 20px; | ||||
|  | ||||
| } | ||||
|  | ||||
| /* Exam analysis */ | ||||
|  | ||||
| #select { | ||||
|     margin-bottom: 20px; | ||||
| } | ||||
|  | ||||
| #select > div { | ||||
|     width: 40vw; | ||||
|     margin: auto; | ||||
| } | ||||
|  | ||||
| #analysis { | ||||
|     display: flex; | ||||
|     flex-flow: row wrap; | ||||
| } | ||||
|  | ||||
| #analysis > * { | ||||
|     display: flex; | ||||
|     flex-flow: column; | ||||
|     width: 45vw; | ||||
|     margin: auto; | ||||
| } | ||||
|  | ||||
| /* Create new exam */ | ||||
|  | ||||
| #new-exam { | ||||
|     display: flex; | ||||
|     flex-flow: row; | ||||
|     justify-content: space-between; | ||||
| } | ||||
|  | ||||
| #new-exam label { | ||||
|     width: 20%; | ||||
|     display: flex; | ||||
|     flex-flow: column; | ||||
|     justify-content: space-between; | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										0
									
								
								recopytex/dashboard/create_exam/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								recopytex/dashboard/create_exam/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										355
									
								
								recopytex/dashboard/create_exam/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								recopytex/dashboard/create_exam/app.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,355 @@ | ||||
| #!/usr/bin/env python | ||||
| # encoding: utf-8 | ||||
|  | ||||
| import dash | ||||
| import dash_html_components as html | ||||
| import dash_core_components as dcc | ||||
| import dash_table | ||||
| import plotly.graph_objects as go | ||||
| from datetime import date, datetime | ||||
| import uuid | ||||
| import pandas as pd | ||||
| import yaml | ||||
|  | ||||
| from ...scripts.getconfig import config | ||||
| from ...config import NO_ST_COLUMNS | ||||
| from ..app import app | ||||
| from ...scripts.exam import Exam | ||||
|  | ||||
| QUESTION_COLUMNS = [ | ||||
|     {"id": "id", "name": "Question"}, | ||||
|     { | ||||
|         "id": "competence", | ||||
|         "name": "Competence", | ||||
|         "presentation": "dropdown", | ||||
|     }, | ||||
|     {"id": "theme", "name": "Domaine"}, | ||||
|     {"id": "comment", "name": "Commentaire"}, | ||||
|     {"id": "score_rate", "name": "Bareme"}, | ||||
|     {"id": "is_leveled", "name": "Est_nivele"}, | ||||
| ] | ||||
|  | ||||
|  | ||||
| def get_current_year_limit(): | ||||
|     today = date.today() | ||||
|     if today.month > 8: | ||||
|         return { | ||||
|             "min_date_allowed": date(today.year, 9, 1), | ||||
|             "max_date_allowed": date(today.year + 1, 7, 15), | ||||
|             "initial_visible_month": today, | ||||
|         } | ||||
|  | ||||
|     return { | ||||
|         "min_date_allowed": date(today.year - 1, 9, 1), | ||||
|         "max_date_allowed": date(today.year, 7, 15), | ||||
|         "initial_visible_month": today, | ||||
|     } | ||||
|  | ||||
|  | ||||
| layout = html.Div( | ||||
|     [ | ||||
|         html.Header( | ||||
|             children=[ | ||||
|                 html.H1("Création d'une évaluation"), | ||||
|                 html.P("Pas encore de sauvegarde", id="is-saved"), | ||||
|                 html.Button("Enregistrer dans csv", id="save-csv"), | ||||
|             ], | ||||
|         ), | ||||
|         html.Main( | ||||
|             children=[ | ||||
|                 html.Section( | ||||
|                     children=[ | ||||
|                         html.Form( | ||||
|                             id="new-exam", | ||||
|                             children=[ | ||||
|                                 html.Label( | ||||
|                                     children=[ | ||||
|                                         "Classe", | ||||
|                                         dcc.Dropdown( | ||||
|                                             id="tribe", | ||||
|                                             options=[ | ||||
|                                                 {"label": t["name"], "value": t["name"]} | ||||
|                                                 for t in config["tribes"] | ||||
|                                             ], | ||||
|                                             value=config["tribes"][0]["name"], | ||||
|                                         ), | ||||
|                                     ] | ||||
|                                 ), | ||||
|                                 html.Label( | ||||
|                                     children=[ | ||||
|                                         "Nom de l'évaluation", | ||||
|                                         dcc.Input( | ||||
|                                             id="exam_name", | ||||
|                                             type="text", | ||||
|                                             placeholder="Nom de l'évaluation", | ||||
|                                         ), | ||||
|                                     ] | ||||
|                                 ), | ||||
|                                 html.Label( | ||||
|                                     children=[ | ||||
|                                         "Date", | ||||
|                                         dcc.DatePickerSingle( | ||||
|                                             id="date", | ||||
|                                             date=date.today(), | ||||
|                                             **get_current_year_limit(), | ||||
|                                         ), | ||||
|                                     ] | ||||
|                                 ), | ||||
|                                 html.Label( | ||||
|                                     children=[ | ||||
|                                         "Trimestre", | ||||
|                                         dcc.Dropdown( | ||||
|                                             id="term", | ||||
|                                             options=[ | ||||
|                                                 {"label": i + 1, "value": i + 1} | ||||
|                                                 for i in range(3) | ||||
|                                             ], | ||||
|                                             value=1, | ||||
|                                         ), | ||||
|                                     ] | ||||
|                                 ), | ||||
|                             ], | ||||
|                         ), | ||||
|                     ], | ||||
|                     id="form", | ||||
|                 ), | ||||
|                 html.Section( | ||||
|                     children=[ | ||||
|                         html.Div( | ||||
|                             id="exercises", | ||||
|                             children=[], | ||||
|                         ), | ||||
|                         html.Button( | ||||
|                             "Ajouter un exercice", | ||||
|                             id="add-exercise", | ||||
|                             className="add-exercise", | ||||
|                         ), | ||||
|                         html.Div( | ||||
|                             id="summary", | ||||
|                         ), | ||||
|                     ], | ||||
|                     id="exercises", | ||||
|                 ), | ||||
|                 html.Section( | ||||
|                     children=[ | ||||
|                         html.Div( | ||||
|                             id="score_rate", | ||||
|                         ), | ||||
|                         html.Div( | ||||
|                             id="exercises-viz", | ||||
|                         ), | ||||
|                         html.Div( | ||||
|                             id="competences-viz", | ||||
|                         ), | ||||
|                         html.Div( | ||||
|                             id="themes-viz", | ||||
|                         ), | ||||
|                     ], | ||||
|                     id="visualisation", | ||||
|                 ), | ||||
|             ] | ||||
|         ), | ||||
|         dcc.Store(id="exam_store"), | ||||
|     ] | ||||
| ) | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     dash.dependencies.Output("exercises", "children"), | ||||
|     dash.dependencies.Input("add-exercise", "n_clicks"), | ||||
|     dash.dependencies.State("exercises", "children"), | ||||
| ) | ||||
| def add_exercise(n_clicks, children): | ||||
|     if n_clicks is None: | ||||
|         return children | ||||
|     element_table = pd.DataFrame(columns=[c["id"] for c in QUESTION_COLUMNS]) | ||||
|     element_table = element_table.append( | ||||
|         pd.Series( | ||||
|             data={ | ||||
|                 "id": 1, | ||||
|                 "competence": "Rechercher", | ||||
|                 "theme": "", | ||||
|                 "comment": "", | ||||
|                 "score_rate": 1, | ||||
|                 "is_leveled": 1, | ||||
|             }, | ||||
|             name=0, | ||||
|         ) | ||||
|     ) | ||||
|     new_exercise = html.Div( | ||||
|         children=[ | ||||
|             html.Div( | ||||
|                 children=[ | ||||
|                     dcc.Input( | ||||
|                         id={"type": "exercice", "index": str(n_clicks)}, | ||||
|                         type="text", | ||||
|                         value=f"Exercice {len(children)+1}", | ||||
|                         placeholder="Nom de l'exercice", | ||||
|                         className="exercise-name", | ||||
|                     ), | ||||
|                     html.Button( | ||||
|                         "X", | ||||
|                         id={"type": "rm_exercice", "index": str(n_clicks)}, | ||||
|                         className="delete-exercise", | ||||
|                     ), | ||||
|                 ], | ||||
|                 className="exercise-head", | ||||
|             ), | ||||
|             dash_table.DataTable( | ||||
|                 id={"type": "elements", "index": str(n_clicks)}, | ||||
|                 columns=QUESTION_COLUMNS, | ||||
|                 data=element_table.to_dict("records"), | ||||
|                 editable=True, | ||||
|                 row_deletable=True, | ||||
|                 dropdown={ | ||||
|                     "competence": { | ||||
|                         "options": [ | ||||
|                             {"label": i, "value": i} for i in config["competences"] | ||||
|                         ] | ||||
|                     }, | ||||
|                 }, | ||||
|                 style_cell={ | ||||
|                     "whiteSpace": "normal", | ||||
|                     "height": "auto", | ||||
|                 }, | ||||
|             ), | ||||
|             html.Button( | ||||
|                 "Ajouter un élément de notation", | ||||
|                 id={"type": "add-element", "index": str(n_clicks)}, | ||||
|                 className="add-element", | ||||
|             ), | ||||
|         ], | ||||
|         className="exercise", | ||||
|         id=f"exercise-{n_clicks}", | ||||
|     ) | ||||
|     children.append(new_exercise) | ||||
|     return children | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     dash.dependencies.Output( | ||||
|         {"type": "elements", "index": dash.dependencies.MATCH}, "data" | ||||
|     ), | ||||
|     dash.dependencies.Input( | ||||
|         {"type": "add-element", "index": dash.dependencies.MATCH}, "n_clicks" | ||||
|     ), | ||||
|     [ | ||||
|         dash.dependencies.State( | ||||
|             {"type": "elements", "index": dash.dependencies.MATCH}, "data" | ||||
|         ), | ||||
|     ], | ||||
|     prevent_initial_call=True, | ||||
| ) | ||||
| def add_element(n_clicks, elements): | ||||
|     if n_clicks is None or n_clicks < len(elements): | ||||
|         return elements | ||||
|  | ||||
|     df = pd.DataFrame.from_records(elements) | ||||
|     df = df.append( | ||||
|         pd.Series( | ||||
|             data={ | ||||
|                 "id": len(df) + 1, | ||||
|                 "competence": "", | ||||
|                 "theme": "", | ||||
|                 "comment": "", | ||||
|                 "score_rate": 1, | ||||
|                 "is_leveled": 1, | ||||
|             }, | ||||
|             name=n_clicks, | ||||
|         ) | ||||
|     ) | ||||
|     return df.to_dict("records") | ||||
|  | ||||
|  | ||||
| def exam_generalities(tribe, exam_name, date, term, exercices=[], elements=[]): | ||||
|     return [ | ||||
|         html.H1(f"{exam_name} pour les {tribe}"), | ||||
|         html.P(f"Fait le {date} (Trimestre {term})"), | ||||
|     ] | ||||
|  | ||||
|  | ||||
| def exercise_summary(identifier, name, elements=[]): | ||||
|     df = pd.DataFrame.from_records(elements) | ||||
|     return html.Div( | ||||
|         [ | ||||
|             html.H2(name), | ||||
|             dash_table.DataTable( | ||||
|                 columns=[{"id": c, "name": c} for c in df], data=elements | ||||
|             ), | ||||
|         ] | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     dash.dependencies.Output("exam_store", "data"), | ||||
|     [ | ||||
|         dash.dependencies.Input("tribe", "value"), | ||||
|         dash.dependencies.Input("exam_name", "value"), | ||||
|         dash.dependencies.Input("date", "date"), | ||||
|         dash.dependencies.Input("term", "value"), | ||||
|         dash.dependencies.Input( | ||||
|             {"type": "exercice", "index": dash.dependencies.ALL}, "value" | ||||
|         ), | ||||
|         dash.dependencies.Input( | ||||
|             {"type": "elements", "index": dash.dependencies.ALL}, "data" | ||||
|         ), | ||||
|     ], | ||||
|     dash.dependencies.State({"type": "elements", "index": dash.dependencies.ALL}, "id"), | ||||
| ) | ||||
| def store_exam(tribe, exam_name, date, term, exercices, elements, elements_id): | ||||
|     exam = Exam(exam_name, tribe, date, term) | ||||
|     for (i, name) in enumerate(exercices): | ||||
|         ex_elements_id = [el for el in elements_id if el["index"] == str(i + 1)][0] | ||||
|         index = elements_id.index(ex_elements_id) | ||||
|         ex_elements = elements[index] | ||||
|         exam.add_exercise(name, ex_elements) | ||||
|  | ||||
|     return exam.to_dict() | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     dash.dependencies.Output("score_rate", "children"), | ||||
|     dash.dependencies.Input("exam_store", "data"), | ||||
|     prevent_initial_call=True, | ||||
| ) | ||||
| def score_rate(data): | ||||
|     exam = Exam(**data) | ||||
|     return [html.P(f"Barème /{exam.score_rate}")] | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     dash.dependencies.Output("competences-viz", "figure"), | ||||
|     dash.dependencies.Input("exam_store", "data"), | ||||
|     prevent_initial_call=True, | ||||
| ) | ||||
| def competences_viz(data): | ||||
|     exam = Exam(**data) | ||||
|     return [html.P(str(exam.competences_rate))] | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     dash.dependencies.Output("themes-viz", "children"), | ||||
|     dash.dependencies.Input("exam_store", "data"), | ||||
|     prevent_initial_call=True, | ||||
| ) | ||||
| def themes_viz(data): | ||||
|     exam = Exam(**data) | ||||
|     themes_rate = exam.themes_rate | ||||
|     fig = go.Figure() | ||||
|     if themes_rate: | ||||
|         fig.add_trace(go.Pie(labels=list(themes_rate.keys()), values=list(themes_rate.values()))) | ||||
|         return [dcc.Graph(figure=fig)] | ||||
|     return [] | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     dash.dependencies.Output("is-saved", "children"), | ||||
|     dash.dependencies.Input("save-csv", "n_clicks"), | ||||
|     dash.dependencies.State("exam_store", "data"), | ||||
|     prevent_initial_call=True, | ||||
| ) | ||||
| def save_to_csv(n_clicks, data): | ||||
|     exam = Exam(**data) | ||||
|     csv = exam.path(".csv") | ||||
|     exam.write_csv() | ||||
|     return [f"Dernière sauvegarde {datetime.today()} dans {csv}"] | ||||
							
								
								
									
										0
									
								
								recopytex/dashboard/exam_analysis/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								recopytex/dashboard/exam_analysis/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										399
									
								
								recopytex/dashboard/exam_analysis/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								recopytex/dashboard/exam_analysis/app.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,399 @@ | ||||
| #!/usr/bin/env python | ||||
| # encoding: utf-8 | ||||
|  | ||||
| import dash | ||||
| import dash_html_components as html | ||||
| import dash_core_components as dcc | ||||
| import dash_table | ||||
| from dash.exceptions import PreventUpdate | ||||
| import plotly.graph_objects as go | ||||
| from pathlib import Path | ||||
| from datetime import datetime | ||||
| import pandas as pd | ||||
| import numpy as np | ||||
|  | ||||
|  | ||||
| from ... import flat_df_students, pp_q_scores | ||||
| from ...config import NO_ST_COLUMNS | ||||
| from ...scripts.getconfig import config | ||||
| from ..app import app | ||||
|  | ||||
| COLORS = { | ||||
|     ".": "black", | ||||
|     0: "#E7472B", | ||||
|     1: "#FF712B", | ||||
|     2: "#F2EC4C", | ||||
|     3: "#68D42F", | ||||
| } | ||||
|  | ||||
| layout = html.Div( | ||||
|     children=[ | ||||
|         html.Header( | ||||
|             children=[ | ||||
|                 html.H1("Analyse des notes"), | ||||
|                 html.P("Dernière sauvegarde", id="lastsave"), | ||||
|             ], | ||||
|         ), | ||||
|         html.Main( | ||||
|             [ | ||||
|                 html.Section( | ||||
|                     [ | ||||
|                         html.Div( | ||||
|                             [ | ||||
|                                 "Classe: ", | ||||
|                                 dcc.Dropdown( | ||||
|                                     id="tribe", | ||||
|                                     options=[ | ||||
|                                         {"label": t["name"], "value": t["name"]} | ||||
|                                         for t in config["tribes"] | ||||
|                                     ], | ||||
|                                     value=config["tribes"][0]["name"], | ||||
|                                 ), | ||||
|                             ], | ||||
|                             style={ | ||||
|                                 "display": "flex", | ||||
|                                 "flex-flow": "column", | ||||
|                             }, | ||||
|                         ), | ||||
|                         html.Div( | ||||
|                             [ | ||||
|                                 "Evaluation: ", | ||||
|                                 dcc.Dropdown(id="csv"), | ||||
|                             ], | ||||
|                             style={ | ||||
|                                 "display": "flex", | ||||
|                                 "flex-flow": "column", | ||||
|                             }, | ||||
|                         ), | ||||
|                     ], | ||||
|                     id="select", | ||||
|                     style={ | ||||
|                         "display": "flex", | ||||
|                         "flex-flow": "row wrap", | ||||
|                     }, | ||||
|                 ), | ||||
|                 html.Div( | ||||
|                     [ | ||||
|                         html.Div( | ||||
|                             dash_table.DataTable( | ||||
|                                 id="final_score_table", | ||||
|                                 columns=[ | ||||
|                                     {"id": "Eleve", "name": "Élève"}, | ||||
|                                     {"id": "Note", "name": "Note"}, | ||||
|                                     {"id": "Bareme", "name": "Barème"}, | ||||
|                                 ], | ||||
|                                 data=[], | ||||
|                                 style_data_conditional=[ | ||||
|                                     { | ||||
|                                         "if": {"row_index": "odd"}, | ||||
|                                         "backgroundColor": "rgb(248, 248, 248)", | ||||
|                                     } | ||||
|                                 ], | ||||
|                                 style_data={ | ||||
|                                     "width": "100px", | ||||
|                                     "maxWidth": "100px", | ||||
|                                     "minWidth": "100px", | ||||
|                                 }, | ||||
|                             ), | ||||
|                             id="final_score_table_container", | ||||
|                         ), | ||||
|                         html.Div( | ||||
|                             [ | ||||
|                                 dash_table.DataTable( | ||||
|                                     id="final_score_describe", | ||||
|                                     columns=[ | ||||
|                                         {"id": "count", "name": "count"}, | ||||
|                                         {"id": "mean", "name": "mean"}, | ||||
|                                         {"id": "std", "name": "std"}, | ||||
|                                         {"id": "min", "name": "min"}, | ||||
|                                         {"id": "25%", "name": "25%"}, | ||||
|                                         {"id": "50%", "name": "50%"}, | ||||
|                                         {"id": "75%", "name": "75%"}, | ||||
|                                         {"id": "max", "name": "max"}, | ||||
|                                     ], | ||||
|                                 ), | ||||
|                                 dcc.Graph( | ||||
|                                     id="fig_assessment_hist", | ||||
|                                 ), | ||||
|                                 dcc.Graph(id="fig_competences"), | ||||
|                             ], | ||||
|                             id="desc_plots", | ||||
|                         ), | ||||
|                     ], | ||||
|                     id="analysis", | ||||
|                 ), | ||||
|                 html.Div( | ||||
|                     [ | ||||
|                         dash_table.DataTable( | ||||
|                             id="scores_table", | ||||
|                             columns=[ | ||||
|                                 {"id": "id", "name": "Question"}, | ||||
|                                 { | ||||
|                                     "id": "competence", | ||||
|                                     "name": "Competence", | ||||
|                                 }, | ||||
|                                 {"id": "theme", "name": "Domaine"}, | ||||
|                                 {"id": "comment", "name": "Commentaire"}, | ||||
|                                 {"id": "score_rate", "name": "Bareme"}, | ||||
|                                 {"id": "is_leveled", "name": "Est_nivele"}, | ||||
|                             ], | ||||
|                             style_cell={ | ||||
|                                 "whiteSpace": "normal", | ||||
|                                 "height": "auto", | ||||
|                             }, | ||||
|                             fixed_columns={"headers": True, "data": 7}, | ||||
|                             style_table={"minWidth": "100%"}, | ||||
|                             style_data_conditional=[], | ||||
|                             editable=True, | ||||
|                         ), | ||||
|                         html.Button("Ajouter un élément", id="btn_add_element"), | ||||
|                     ], | ||||
|                     id="big_table", | ||||
|                 ), | ||||
|                 dcc.Store(id="final_score"), | ||||
|             ], | ||||
|             className="content", | ||||
|             style={ | ||||
|                 "width": "95vw", | ||||
|                 "margin": "auto", | ||||
|             }, | ||||
|         ), | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     [ | ||||
|         dash.dependencies.Output("csv", "options"), | ||||
|         dash.dependencies.Output("csv", "value"), | ||||
|     ], | ||||
|     [dash.dependencies.Input("tribe", "value")], | ||||
| ) | ||||
| def update_csvs(value): | ||||
|     if not value: | ||||
|         raise PreventUpdate | ||||
|     p = Path(value) | ||||
|     csvs = list(p.glob("*.csv")) | ||||
|     try: | ||||
|         return [{"label": str(c), "value": str(c)} for c in csvs], str(csvs[0]) | ||||
|     except IndexError: | ||||
|         return [] | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     [ | ||||
|         dash.dependencies.Output("final_score", "data"), | ||||
|     ], | ||||
|     [dash.dependencies.Input("scores_table", "data")], | ||||
| ) | ||||
| def update_final_scores(data): | ||||
|     if not data: | ||||
|         raise PreventUpdate | ||||
|  | ||||
|     scores = pd.DataFrame.from_records(data) | ||||
|     try: | ||||
|         if scores.iloc[0]["Commentaire"] == "commentaire": | ||||
|             scores.drop([0], inplace=True) | ||||
|     except KeyError: | ||||
|         pass | ||||
|     scores = flat_df_students(scores).dropna(subset=["Score"]) | ||||
|     if scores.empty: | ||||
|         return [{}] | ||||
|  | ||||
|     scores = pp_q_scores(scores) | ||||
|     assessment_scores = scores.groupby(["Eleve"]).agg({"Note": "sum", "Bareme": "sum"}) | ||||
|     return [assessment_scores.reset_index().to_dict("records")] | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     [ | ||||
|         dash.dependencies.Output("final_score_table", "data"), | ||||
|     ], | ||||
|     [dash.dependencies.Input("final_score", "data")], | ||||
| ) | ||||
| def update_final_scores_table(data): | ||||
|     assessment_scores = pd.DataFrame.from_records(data) | ||||
|     return [assessment_scores.to_dict("records")] | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     [ | ||||
|         dash.dependencies.Output("final_score_describe", "data"), | ||||
|     ], | ||||
|     [dash.dependencies.Input("final_score", "data")], | ||||
| ) | ||||
| def update_final_scores_descr(data): | ||||
|     scores = pd.DataFrame.from_records(data) | ||||
|     if scores.empty: | ||||
|         return [[{}]] | ||||
|     desc = scores["Note"].describe().T.round(2) | ||||
|     return [[desc.to_dict()]] | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     [ | ||||
|         dash.dependencies.Output("fig_assessment_hist", "figure"), | ||||
|     ], | ||||
|     [dash.dependencies.Input("final_score", "data")], | ||||
| ) | ||||
| def update_final_scores_hist(data): | ||||
|     assessment_scores = pd.DataFrame.from_records(data) | ||||
|  | ||||
|     if assessment_scores.empty: | ||||
|         return [{"data": [], "layout": []}] | ||||
|  | ||||
|     ranges = np.linspace( | ||||
|         -0.5, | ||||
|         assessment_scores.Bareme.max(), | ||||
|         int(assessment_scores.Bareme.max() * 2 + 2), | ||||
|     ) | ||||
|     bins = pd.cut(assessment_scores["Note"], ranges) | ||||
|     assessment_scores["Bin"] = bins | ||||
|     assessment_grouped = ( | ||||
|         assessment_scores.reset_index() | ||||
|         .groupby("Bin") | ||||
|         .agg({"Bareme": "count", "Eleve": lambda x: "\n".join(x)}) | ||||
|     ) | ||||
|     assessment_grouped.index = assessment_grouped.index.map(lambda i: i.right) | ||||
|     fig = go.Figure() | ||||
|     fig.add_bar( | ||||
|         x=assessment_grouped.index, | ||||
|         y=assessment_grouped.Bareme, | ||||
|         text=assessment_grouped.Eleve, | ||||
|         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( | ||||
|     [ | ||||
|         dash.dependencies.Output("fig_competences", "figure"), | ||||
|     ], | ||||
|     [dash.dependencies.Input("scores_table", "data")], | ||||
| ) | ||||
| def update_competence_fig(data): | ||||
|     scores = pd.DataFrame.from_records(data) | ||||
|     try: | ||||
|         if scores.iloc[0]["Commentaire"] == "commentaire": | ||||
|             scores.drop([0], inplace=True) | ||||
|     except KeyError: | ||||
|         pass | ||||
|     scores = flat_df_students(scores).dropna(subset=["Score"]) | ||||
|  | ||||
|     if scores.empty: | ||||
|         return [{"data": [], "layout": []}] | ||||
|  | ||||
|     scores = pp_q_scores(scores) | ||||
|     pt = pd.pivot_table( | ||||
|         scores, | ||||
|         index=["Exercice", "Question", "Commentaire"], | ||||
|         columns="Score", | ||||
|         aggfunc="size", | ||||
|         fill_value=0, | ||||
|     ) | ||||
|     for i in {i for i in pt.index.get_level_values(0)}: | ||||
|         pt.loc[(str(i), "", ""), :] = "" | ||||
|     pt.sort_index(inplace=True) | ||||
|     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 = [ | ||||
|         {"score": -1, "name": "Pas de réponse", "color": COLORS["."]}, | ||||
|         {"score": 0, "name": "Faux", "color": COLORS[0]}, | ||||
|         {"score": 1, "name": "Peu juste", "color": COLORS[1]}, | ||||
|         {"score": 2, "name": "Presque juste", "color": COLORS[2]}, | ||||
|         {"score": 3, "name": "Juste", "color": COLORS[3]}, | ||||
|     ] | ||||
|     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] | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     [ | ||||
|         dash.dependencies.Output("lastsave", "children"), | ||||
|     ], | ||||
|     [ | ||||
|         dash.dependencies.Input("scores_table", "data"), | ||||
|         dash.dependencies.State("csv", "value"), | ||||
|     ], | ||||
| ) | ||||
| def save_scores(data, csv): | ||||
|     try: | ||||
|         scores = pd.DataFrame.from_records(data) | ||||
|         scores.to_csv(csv, index=False) | ||||
|     except: | ||||
|         return [f"Soucis pour sauvegarder à  {datetime.today()} dans {csv}"] | ||||
|     else: | ||||
|         return [f"Dernière sauvegarde {datetime.today()} dans {csv}"] | ||||
|  | ||||
|  | ||||
| def highlight_value(df): | ||||
|     """ Cells style  """ | ||||
|     hight = [] | ||||
|     for v, color in COLORS.items(): | ||||
|         hight += [ | ||||
|             { | ||||
|                 "if": {"filter_query": "{{{}}} = {}".format(col, v), "column_id": col}, | ||||
|                 "backgroundColor": color, | ||||
|                 "color": "white", | ||||
|             } | ||||
|             for col in df.columns | ||||
|             if col not in NO_ST_COLUMNS.values() | ||||
|         ] | ||||
|     return hight | ||||
|  | ||||
|  | ||||
| @app.callback( | ||||
|     [ | ||||
|         dash.dependencies.Output("scores_table", "columns"), | ||||
|         dash.dependencies.Output("scores_table", "data"), | ||||
|         dash.dependencies.Output("scores_table", "style_data_conditional"), | ||||
|     ], | ||||
|     [ | ||||
|         dash.dependencies.Input("csv", "value"), | ||||
|         dash.dependencies.Input("btn_add_element", "n_clicks"), | ||||
|         dash.dependencies.State("scores_table", "data"), | ||||
|     ], | ||||
| ) | ||||
| def update_scores_table(csv, add_element, data): | ||||
|     ctx = dash.callback_context | ||||
|     if ctx.triggered[0]["prop_id"] == "csv.value": | ||||
|         stack = pd.read_csv(csv, encoding="UTF8") | ||||
|     elif ctx.triggered[0]["prop_id"] == "btn_add_element.n_clicks": | ||||
|         stack = pd.DataFrame.from_records(data) | ||||
|         infos = pd.DataFrame.from_records( | ||||
|             [{k: stack.iloc[-1][k] for k in NO_ST_COLUMNS.values()}] | ||||
|         ) | ||||
|         stack = stack.append(infos) | ||||
|     return ( | ||||
|         [ | ||||
|             {"id": c, "name": c} | ||||
|             for c in stack.columns | ||||
|             if c not in ["Trimestre", "Nom", "Date"] | ||||
|         ], | ||||
|         stack.to_dict("records"), | ||||
|         highlight_value(stack), | ||||
|     ) | ||||
							
								
								
									
										26
									
								
								recopytex/dashboard/index.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								recopytex/dashboard/index.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import dash_core_components as dcc | ||||
| import dash_html_components as html | ||||
| from dash.dependencies import Input, Output | ||||
|  | ||||
| from .app import app | ||||
| from .exam_analysis import app as exam_analysis | ||||
| from .create_exam import app as create_exam | ||||
|  | ||||
|  | ||||
| app.layout = html.Div( | ||||
|     [dcc.Location(id="url", refresh=False), html.Div(id="page-content")] | ||||
| ) | ||||
|  | ||||
|  | ||||
| @app.callback(Output("page-content", "children"), Input("url", "pathname")) | ||||
| def display_page(pathname): | ||||
|     if pathname == "/": | ||||
|         return exam_analysis.layout | ||||
|     elif pathname == "/create-exam": | ||||
|         return create_exam.layout | ||||
|     else: | ||||
|         return "404" | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     app.run_server(debug=True) | ||||
| @@ -4,9 +4,11 @@ | ||||
| import pandas as pd | ||||
| import numpy as np | ||||
| from math import ceil, floor | ||||
| from .config import COLUMNS, VALIDSCORE | ||||
| from .config import COLUMNS | ||||
|  | ||||
| # Values manipulations | ||||
| """ | ||||
| Functions for manipulate score dataframes | ||||
| """ | ||||
|  | ||||
|  | ||||
| def round_half_point(val): | ||||
| @@ -19,12 +21,13 @@ def round_half_point(val): | ||||
|  | ||||
|  | ||||
| def score_to_mark(x): | ||||
|     """ Compute the mark | ||||
|     """Compute the mark | ||||
|  | ||||
|     if the item is leveled then the score is multiply by the score_rate | ||||
|     otherwise it copies the score | ||||
|  | ||||
|     :param x: dictionnary with COLUMNS["is_leveled"], COLUMNS["score"] and COLUMNS["score_rate"] keys | ||||
|     :return: the mark | ||||
|  | ||||
|     >>> d = {"Eleve":["E1"]*6 + ["E2"]*6, | ||||
|     ...    COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2, | ||||
| @@ -43,8 +46,11 @@ def score_to_mark(x): | ||||
|  | ||||
|     if x[COLUMNS["is_leveled"]]: | ||||
|         if x[COLUMNS["score"]] not in [0, 1, 2, 3]: | ||||
|             raise ValueError(f"The evaluation is out of range: {x[COLUMNS['score']]} at {x}") | ||||
|         return round_half_point(x[COLUMNS["score"]] * x[COLUMNS["score_rate"]] / 3) | ||||
|             raise ValueError( | ||||
|                 f"The evaluation is out of range: {x[COLUMNS['score']]} at {x}" | ||||
|             ) | ||||
|         return round(x[COLUMNS["score"]] * x[COLUMNS["score_rate"]] / 3, 2) | ||||
|         #return round_half_point(x[COLUMNS["score"]] * x[COLUMNS["score_rate"]] / 3) | ||||
|  | ||||
|     if x[COLUMNS["score"]] > x[COLUMNS["score_rate"]]: | ||||
|         raise ValueError( | ||||
| @@ -54,9 +60,10 @@ def score_to_mark(x): | ||||
|  | ||||
|  | ||||
| def score_to_level(x): | ||||
|     """ Compute the level (".",0,1,2,3). | ||||
|     """Compute the level (".",0,1,2,3). | ||||
|  | ||||
|     :param x: dictionnary with COLUMNS["is_leveled"], COLUMNS["score"] and COLUMNS["score_rate"] keys | ||||
|     :return: the level | ||||
|  | ||||
|     >>> d = {"Eleve":["E1"]*6 + ["E2"]*6, | ||||
|     ...    COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2, | ||||
| @@ -91,7 +98,9 @@ def score_to_level(x): | ||||
|  | ||||
|  | ||||
| def compute_mark(df): | ||||
|     """ Add Mark column to df | ||||
|     """Compute the mark for the dataframe | ||||
|  | ||||
|     apply score_to_mark to each row | ||||
|  | ||||
|     :param df: DataFrame with COLUMNS["score"], COLUMNS["is_leveled"] and COLUMNS["score_rate"] columns. | ||||
|  | ||||
| @@ -122,9 +131,12 @@ def compute_mark(df): | ||||
|  | ||||
|  | ||||
| def compute_level(df): | ||||
|     """ Add Mark column to df | ||||
|     """Compute level for the dataframe | ||||
|  | ||||
|     Applies score_to_level to each row | ||||
|  | ||||
|     :param df: DataFrame with COLUMNS["score"], COLUMNS["is_leveled"] and COLUMNS["score_rate"] columns. | ||||
|     :return: Columns with level | ||||
|  | ||||
|     >>> d = {"Eleve":["E1"]*6 + ["E2"]*6, | ||||
|     ...    COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2, | ||||
| @@ -153,9 +165,10 @@ def compute_level(df): | ||||
|  | ||||
|  | ||||
| def compute_normalized(df): | ||||
|     """ Compute the normalized mark (Mark / score_rate) | ||||
|     """Compute the normalized mark (Mark / score_rate) | ||||
|  | ||||
|     :param df: DataFrame with "Mark" and COLUMNS["score_rate"] columns | ||||
|     :return: column with normalized mark | ||||
|  | ||||
|     >>> d = {"Eleve":["E1"]*6 + ["E2"]*6, | ||||
|     ...    COLUMNS["score_rate"]:[1]*2+[2]*2+[2]*2 + [1]*2+[2]*2+[2]*2, | ||||
| @@ -186,7 +199,9 @@ def compute_normalized(df): | ||||
|  | ||||
|  | ||||
| def pp_q_scores(df): | ||||
|     """ Postprocessing questions scores dataframe | ||||
|     """Postprocessing questions scores dataframe | ||||
|  | ||||
|     Add 3 columns: mark, level and normalized | ||||
|  | ||||
|     :param df: questions-scores dataframe | ||||
|     :return: same data frame with mark, level and normalize columns | ||||
|   | ||||
							
								
								
									
										202
									
								
								recopytex/scripts/exam.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								recopytex/scripts/exam.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| #!/usr/bin/env python | ||||
| # encoding: utf-8 | ||||
|  | ||||
| from datetime import datetime | ||||
| from pathlib import Path | ||||
| from prompt_toolkit import HTML | ||||
| from ..config import NO_ST_COLUMNS | ||||
| import pandas as pd | ||||
| import yaml | ||||
| from .getconfig import config | ||||
|  | ||||
|  | ||||
| def try_parsing_date(text, formats=["%Y-%m-%d", "%Y.%m.%d", "%Y/%m/%d"]): | ||||
|     for fmt in formats: | ||||
|         try: | ||||
|             return datetime.strptime(text[:10], fmt) | ||||
|         except ValueError: | ||||
|             pass | ||||
|     raise ValueError("no valid date format found") | ||||
|  | ||||
|  | ||||
| class Exam: | ||||
|     def __init__(self, name, tribename, date, term, **kwrds): | ||||
|         self._name = name | ||||
|         self._tribename = tribename | ||||
|  | ||||
|         self._date = try_parsing_date(date) | ||||
|  | ||||
|         self._term = term | ||||
|  | ||||
|         try: | ||||
|             kwrds["exercices"] | ||||
|         except KeyError: | ||||
|             self._exercises = {} | ||||
|         else: | ||||
|             self._exercises = kwrds["exercices"] | ||||
|  | ||||
|     @property | ||||
|     def name(self): | ||||
|         return self._name | ||||
|  | ||||
|     @property | ||||
|     def tribename(self): | ||||
|         return self._tribename | ||||
|  | ||||
|     @property | ||||
|     def date(self): | ||||
|         return self._date | ||||
|  | ||||
|     @property | ||||
|     def term(self): | ||||
|         return self._term | ||||
|  | ||||
|     def add_exercise(self, name, questions): | ||||
|         """ Add key with questions in ._exercises """ | ||||
|         try: | ||||
|             self._exercises[name] | ||||
|         except KeyError: | ||||
|             self._exercises[name] = questions | ||||
|         else: | ||||
|             raise KeyError("The exercise already exsists. Use modify_exercise") | ||||
|  | ||||
|     def modify_exercise(self, name, questions, append=False): | ||||
|         """Modify questions of an exercise | ||||
|  | ||||
|         If append==True, add questions to the exercise questions | ||||
|  | ||||
|         """ | ||||
|         try: | ||||
|             self._exercises[name] | ||||
|         except KeyError: | ||||
|             raise KeyError("The exercise already exsists. Use modify_exercise") | ||||
|         else: | ||||
|             if append: | ||||
|                 self._exercises[name] += questions | ||||
|             else: | ||||
|                 self._exercises[name] = questions | ||||
|  | ||||
|     @property | ||||
|     def exercices(self): | ||||
|         return self._exercises | ||||
|  | ||||
|     @property | ||||
|     def tribe_path(self): | ||||
|         return Path(config["source"]) / self.tribename | ||||
|  | ||||
|     @property | ||||
|     def tribe_student_path(self): | ||||
|         return ( | ||||
|             Path(config["source"]) | ||||
|             / [t["students"] for t in config["tribes"] if t["name"] == self.tribename][ | ||||
|                 0 | ||||
|             ] | ||||
|         ) | ||||
|  | ||||
|     @property | ||||
|     def long_name(self): | ||||
|         """ Get exam name with date inside """ | ||||
|         return f"{self.date.strftime('%y%m%d')}_{self.name}" | ||||
|  | ||||
|     def path(self, extention=""): | ||||
|         return self.tribe_path / (self.long_name + extention) | ||||
|  | ||||
|     def to_dict(self): | ||||
|         return { | ||||
|             "name": self.name, | ||||
|             "tribename": self.tribename, | ||||
|             "date": self.date, | ||||
|             "term": self.term, | ||||
|             "exercices": self.exercices, | ||||
|         } | ||||
|  | ||||
|     def to_row(self): | ||||
|         rows = [] | ||||
|         for ex, questions in self.exercices.items(): | ||||
|             for q in questions: | ||||
|                 rows.append( | ||||
|                     { | ||||
|                         "term": self.term, | ||||
|                         "assessment": self.name, | ||||
|                         "date": self.date.strftime("%d/%m/%Y"), | ||||
|                         "exercise": ex, | ||||
|                         "question": q["id"], | ||||
|                         **q, | ||||
|                     } | ||||
|                 ) | ||||
|         return rows | ||||
|  | ||||
|     @property | ||||
|     def themes(self): | ||||
|         themes = set() | ||||
|         for questions in self._exercises.values(): | ||||
|             themes.update([q["theme"] for q in questions]) | ||||
|         return themes | ||||
|  | ||||
|     def display_exercise(self, name): | ||||
|         pass | ||||
|  | ||||
|     def display(self, name): | ||||
|         pass | ||||
|  | ||||
|     def write_yaml(self): | ||||
|         print(f"Sauvegarde temporaire dans {self.path('.yml')}") | ||||
|         self.tribe_path.mkdir(exist_ok=True) | ||||
|         with open(self.path(".yml"), "w") as f: | ||||
|             f.write(yaml.dump(self.to_dict())) | ||||
|  | ||||
|     def write_csv(self): | ||||
|         rows = self.to_row() | ||||
|  | ||||
|         base_df = pd.DataFrame.from_dict(rows)[NO_ST_COLUMNS.keys()] | ||||
|         base_df.rename(columns=NO_ST_COLUMNS, inplace=True) | ||||
|  | ||||
|         students = pd.read_csv(self.tribe_student_path)["Nom"] | ||||
|         for student in students: | ||||
|             base_df[student] = "" | ||||
|  | ||||
|         self.tribe_path.mkdir(exist_ok=True) | ||||
|         base_df.to_csv(self.path(".csv"), index=False) | ||||
|  | ||||
|     @property | ||||
|     def score_rate(self): | ||||
|         total = 0 | ||||
|         for ex, questions in self._exercises.items(): | ||||
|             total += sum([q["score_rate"] for q in questions]) | ||||
|  | ||||
|         return total | ||||
|  | ||||
|     @property | ||||
|     def competences_rate(self): | ||||
|         """ Dictionnary with competences as key and total rate as value""" | ||||
|         rates = {} | ||||
|         for ex, questions in self._exercises.items(): | ||||
|             for q in questions: | ||||
|                 try: | ||||
|                     q["competence"] | ||||
|                 except KeyError: | ||||
|                     pass | ||||
|                 else: | ||||
|                     try: | ||||
|                         rates[q["competence"]] += q["score_rate"] | ||||
|                     except KeyError: | ||||
|                         rates[q["competence"]] = q["score_rate"] | ||||
|         return rates | ||||
|  | ||||
|     @property | ||||
|     def themes_rate(self): | ||||
|         """ Dictionnary with themes as key and total rate as value""" | ||||
|         rates = {} | ||||
|         for ex, questions in self._exercises.items(): | ||||
|             for q in questions: | ||||
|                 try: | ||||
|                     q["theme"] | ||||
|                 except KeyError: | ||||
|                     pass | ||||
|                 else: | ||||
|                     if q["theme"]: | ||||
|                         try: | ||||
|                             rates[q["theme"]] += q["score_rate"] | ||||
|                         except KeyError: | ||||
|                             rates[q["theme"]] = q["score_rate"] | ||||
|         return rates | ||||
							
								
								
									
										9
									
								
								recopytex/scripts/getconfig.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								recopytex/scripts/getconfig.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| #!/usr/bin/env python | ||||
| # encoding: utf-8 | ||||
| import yaml | ||||
|  | ||||
| CONFIGPATH = "recoconfig.yml" | ||||
|  | ||||
| with open(CONFIGPATH, "r") as config: | ||||
|     config = yaml.load(config, Loader=yaml.FullLoader) | ||||
|  | ||||
							
								
								
									
										233
									
								
								recopytex/scripts/prompts.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								recopytex/scripts/prompts.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | ||||
| #!/usr/bin/env python | ||||
| # encoding: utf-8 | ||||
|  | ||||
|  | ||||
| from prompt_toolkit import prompt, HTML, ANSI | ||||
| from prompt_toolkit import print_formatted_text as print | ||||
| from prompt_toolkit.styles import Style | ||||
| from prompt_toolkit.validation import Validator | ||||
| from prompt_toolkit.completion import WordCompleter | ||||
| from unidecode import unidecode | ||||
| from datetime import datetime | ||||
| from functools import wraps | ||||
| import sys | ||||
|  | ||||
| from .getconfig import config | ||||
|  | ||||
|  | ||||
| VALIDATE = [ | ||||
|     "o", | ||||
|     "ok", | ||||
|     "OK", | ||||
|     "oui", | ||||
|     "OUI", | ||||
|     "yes", | ||||
|     "YES", | ||||
| ] | ||||
| REFUSE = ["n", "non", "NON", "no", "NO"] | ||||
| CANCEL = ["a", "annuler"] | ||||
|  | ||||
| STYLE = Style.from_dict( | ||||
|     { | ||||
|         "": "#93A1A1", | ||||
|         "validation": "#884444", | ||||
|         "appending": "#448844", | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| class CancelError(Exception): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| def prompt_validate(question, cancelable=False, empty_means=1, style="validation"): | ||||
|     """Prompt for validation | ||||
|  | ||||
|     :param question: Text to print to ask the question. | ||||
|     :param cancelable: enable cancel answer | ||||
|     :param empty_means: result for no answer | ||||
|     :return: | ||||
|         0 -> Refuse | ||||
|         1 -> Validate | ||||
|         -1 -> cancel | ||||
|     """ | ||||
|     question_ = question | ||||
|     choices = VALIDATE + REFUSE | ||||
|  | ||||
|     if cancelable: | ||||
|         question_ += "(a ou annuler pour sortir)" | ||||
|         choices += CANCEL | ||||
|  | ||||
|     ans = prompt( | ||||
|         [ | ||||
|             (f"class:{style}", question_), | ||||
|         ], | ||||
|         completer=WordCompleter(choices), | ||||
|         style=STYLE, | ||||
|     ).lower() | ||||
|  | ||||
|     if ans == "": | ||||
|         return empty_means | ||||
|     if ans in VALIDATE: | ||||
|         return 1 | ||||
|     if cancelable and ans in CANCEL: | ||||
|         return -1 | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def prompt_until_validate(question="C'est ok? ", cancelable=False): | ||||
|     def decorator(func): | ||||
|         @wraps(func) | ||||
|         def wrapper(*args, **kwrd): | ||||
|             ans = func(*args, **kwrd) | ||||
|  | ||||
|             confirm = prompt_validate(question, cancelable) | ||||
|  | ||||
|             if confirm == -1: | ||||
|                 raise CancelError | ||||
|  | ||||
|             while not confirm: | ||||
|                 sys.stdout.flush() | ||||
|                 ans = func(*args, **ans, **kwrd) | ||||
|                 confirm = prompt_validate(question, cancelable) | ||||
|                 if confirm == -1: | ||||
|                     raise CancelError | ||||
|             return ans | ||||
|  | ||||
|         return wrapper | ||||
|  | ||||
|     return decorator | ||||
|  | ||||
|  | ||||
| @prompt_until_validate() | ||||
| def prompt_exam(**kwrd): | ||||
|     """ Prompt questions to edit an exam """ | ||||
|     print(HTML("<b>Nouvelle évaluation</b>")) | ||||
|     exam = {} | ||||
|     exam["name"] = prompt("Nom de l'évaluation: ", default=kwrd.get("name", "DS")) | ||||
|  | ||||
|     tribes_name = [t["name"] for t in config["tribes"]] | ||||
|  | ||||
|     exam["tribename"] = prompt( | ||||
|         "Nom de la classe: ", | ||||
|         default=kwrd.get("tribename", ""), | ||||
|         completer=WordCompleter(tribes_name), | ||||
|         validator=Validator.from_callable(lambda x: x in tribes_name), | ||||
|     ) | ||||
|     exam["tribe"] = [t for t in config["tribes"] if t["name"] == exam["tribename"]][0] | ||||
|  | ||||
|     exam["date"] = prompt( | ||||
|         "Date de l'évaluation (%y%m%d): ", | ||||
|         default=kwrd.get("date", datetime.today()).strftime("%y%m%d"), | ||||
|         validator=Validator.from_callable(lambda x: (len(x) == 6) and x.isdigit()), | ||||
|     ) | ||||
|     exam["date"] = datetime.strptime(exam["date"], "%y%m%d") | ||||
|  | ||||
|     exam["term"] = prompt( | ||||
|         "Trimestre: ", | ||||
|         validator=Validator.from_callable(lambda x: x.isdigit()), | ||||
|         default=kwrd.get("term", "1"), | ||||
|     ) | ||||
|  | ||||
|     return exam | ||||
|  | ||||
|  | ||||
| @prompt_until_validate() | ||||
| def prompt_exercise(number=1, completer={}, **kwrd): | ||||
|     exercise = {} | ||||
|     try: | ||||
|         kwrd["name"] | ||||
|     except KeyError: | ||||
|         print(HTML("<b>Nouvel exercice</b>")) | ||||
|         exercise["name"] = prompt( | ||||
|             "Nom de l'exercice: ", default=kwrd.get("name", f"Exercice {number}") | ||||
|         ) | ||||
|     else: | ||||
|         print(HTML(f"<b>Modification de l'exercice: {kwrd['name']}</b>")) | ||||
|         exercise["name"] = kwrd["name"] | ||||
|  | ||||
|     exercise["questions"] = [] | ||||
|  | ||||
|     try: | ||||
|         kwrd["questions"][0] | ||||
|     except KeyError: | ||||
|         last_question_id = "1a" | ||||
|     except IndexError: | ||||
|         last_question_id = "1a" | ||||
|     else: | ||||
|         for ques in kwrd["questions"]: | ||||
|             try: | ||||
|                 exercise["questions"].append( | ||||
|                     prompt_question(completer=completer, **ques) | ||||
|                 ) | ||||
|             except CancelError: | ||||
|                 print("Cette question a été supprimée") | ||||
|         last_question_id = exercise["questions"][-1]["id"] | ||||
|  | ||||
|     appending = prompt_validate( | ||||
|         question="Ajouter un élément de notation? ", style="appending" | ||||
|     ) | ||||
|     while appending: | ||||
|         try: | ||||
|             exercise["questions"].append( | ||||
|                 prompt_question(last_question_id, completer=completer) | ||||
|             ) | ||||
|         except CancelError: | ||||
|             print("Cette question a été supprimée") | ||||
|         else: | ||||
|             last_question_id = exercise["questions"][-1]["id"] | ||||
|         appending = prompt_validate( | ||||
|             question="Ajouter un élément de notation? ", style="appending" | ||||
|         ) | ||||
|  | ||||
|     return exercise | ||||
|  | ||||
|  | ||||
| @prompt_until_validate(cancelable=True) | ||||
| def prompt_question(last_question_id="1a", completer={}, **kwrd): | ||||
|     try: | ||||
|         kwrd["id"] | ||||
|     except KeyError: | ||||
|         print(HTML("<b>Nouvel élément de notation</b>")) | ||||
|     else: | ||||
|         print( | ||||
|             HTML(f"<b>Modification de l'élément {kwrd['id']} ({kwrd['comment']})</b>") | ||||
|         ) | ||||
|  | ||||
|     question = {} | ||||
|     question["id"] = prompt( | ||||
|         "Identifiant de la question: ", | ||||
|         default=kwrd.get("id", "1a"), | ||||
|     ) | ||||
|  | ||||
|     question["competence"] = prompt( | ||||
|         "Competence: ", | ||||
|         default=kwrd.get("competence", list(config["competences"].keys())[0]), | ||||
|         completer=WordCompleter(config["competences"].keys()), | ||||
|         validator=Validator.from_callable(lambda x: x in config["competences"].keys()), | ||||
|     ) | ||||
|  | ||||
|     question["theme"] = prompt( | ||||
|         "Domaine: ", | ||||
|         default=kwrd.get("theme", ""), | ||||
|         completer=WordCompleter(completer.get("theme", [])), | ||||
|     ) | ||||
|  | ||||
|     question["comment"] = prompt( | ||||
|         "Commentaire: ", | ||||
|         default=kwrd.get("comment", ""), | ||||
|     ) | ||||
|  | ||||
|     question["is_leveled"] = prompt( | ||||
|         "Évaluation par niveau: ", | ||||
|         default=kwrd.get("is_leveled", "1"), | ||||
|         # validate | ||||
|     ) | ||||
|  | ||||
|     question["score_rate"] = prompt( | ||||
|         "Barème: ", | ||||
|         default=kwrd.get("score_rate", "1"), | ||||
|         # validate | ||||
|     ) | ||||
|  | ||||
|     return question | ||||
| @@ -3,15 +3,17 @@ | ||||
|  | ||||
| import click | ||||
| from pathlib import Path | ||||
| import yaml | ||||
| import sys | ||||
| import papermill as pm | ||||
| import pandas as pd | ||||
| from datetime import datetime | ||||
| import yaml | ||||
|  | ||||
| CONFIGPATH = "recoconfig.yml" | ||||
|  | ||||
| with open(CONFIGPATH, "r") as config: | ||||
|     config = yaml.load(config, Loader=yaml.FullLoader) | ||||
| from .getconfig import config, CONFIGPATH | ||||
| from .prompts import prompt_exam, prompt_exercise, prompt_validate | ||||
| from ..config import NO_ST_COLUMNS | ||||
| from .exam import Exam | ||||
| from ..dashboard.index import app as dash | ||||
|  | ||||
|  | ||||
| @click.group() | ||||
| @@ -26,6 +28,70 @@ def print_config(): | ||||
|     click.echo(config) | ||||
|  | ||||
|  | ||||
| @cli.command() | ||||
| def setup(): | ||||
|     """Setup the environnement using recoconfig.yml""" | ||||
|     for tribe in config["tribes"]: | ||||
|         Path(tribe["name"]).mkdir(exist_ok=True) | ||||
|         if not Path(tribe["students"]).exists(): | ||||
|             print(f"The file {tribe['students']} does not exists") | ||||
|  | ||||
|  | ||||
| @cli.command() | ||||
| def new_exam(): | ||||
|     """ Create new exam csv file """ | ||||
|     exam = Exam(**prompt_exam()) | ||||
|  | ||||
|     if exam.path(".yml").exists(): | ||||
|         print(f"Fichier sauvegarde trouvé à {exam.path('.yml')} -- importation") | ||||
|         with open(exam.path(".yml"), "r") as f: | ||||
|             for name, questions in yaml.load(f, Loader=yaml.SafeLoader)[ | ||||
|                 "exercices" | ||||
|             ].items(): | ||||
|                 exam.add_exercise(name, questions) | ||||
|  | ||||
|         print(exam.themes) | ||||
|         # print(yaml.dump(exam.to_dict())) | ||||
|  | ||||
|     exam.write() | ||||
|  | ||||
|     for name, questions in exam.exercices.items(): | ||||
|         exam.modify_exercise( | ||||
|             **prompt_exercise( | ||||
|                 name=name, completer={"theme": exam.themes}, questions=questions | ||||
|             ) | ||||
|         ) | ||||
|         exam.write() | ||||
|  | ||||
|     new_exercise = prompt_validate("Ajouter un exercice? ") | ||||
|     while new_exercise: | ||||
|         exam.add_exercise( | ||||
|             **prompt_exercise(len(exam.exercices) + 1, completer={"theme": exam.themes}) | ||||
|         ) | ||||
|         exam.write() | ||||
|         new_exercise = prompt_validate("Ajouter un exercice? ") | ||||
|  | ||||
|     rows = exam.to_row() | ||||
|  | ||||
|     base_df = pd.DataFrame.from_dict(rows)[NO_ST_COLUMNS.keys()] | ||||
|     base_df.rename(columns=NO_ST_COLUMNS, inplace=True) | ||||
|  | ||||
|     students = pd.read_csv(exam.tribe_student_path)["Nom"] | ||||
|     for student in students: | ||||
|         base_df[student] = "" | ||||
|  | ||||
|     exam.tribe_path.mkdir(exist_ok=True) | ||||
|  | ||||
|     base_df.to_csv(exam.path(".csv"), index=False) | ||||
|     print(f"Le fichier note a été enregistré à {exam.path('.csv')}") | ||||
|  | ||||
|  | ||||
| @cli.command() | ||||
| @click.option("--debug", default=0, help="Debug mode for dash") | ||||
| def dashboard(debug): | ||||
|     dash.run_server(debug=bool(debug)) | ||||
|  | ||||
|  | ||||
| @cli.command() | ||||
| @click.argument("csv_file") | ||||
| def report(csv_file): | ||||
| @@ -60,22 +126,9 @@ def report(csv_file): | ||||
|         str(template), | ||||
|         str(dest / f"{assessment}.ipynb"), | ||||
|         parameters=dict( | ||||
|             tribe=tribe, assessment=assessment, date=f"{date:%d/%m/%y}", csv_file=str(csv_file.absolute()) | ||||
|             tribe=tribe, | ||||
|             assessment=assessment, | ||||
|             date=f"{date:%d/%m/%y}", | ||||
|             csv_file=str(csv_file.absolute()), | ||||
|         ), | ||||
|     ) | ||||
|  | ||||
|     # with open(csv_file.parent / "description.yml") as f: | ||||
|     #     tribe_desc = yaml.load(f, Loader=yaml.FullLoader) | ||||
|  | ||||
|     # template = Path(config["templates"]) / "tpl_student.ipynb" | ||||
|     # dest = Path(config["output"]) / tribe / csv_filename / "students" | ||||
|     # dest.mkdir(parents=True, exist_ok=True) | ||||
|  | ||||
|     # for st in tribe_desc["students"]: | ||||
|     #     click.echo(f"Building {st} report on {assessment}") | ||||
|     #     pm.execute_notebook( | ||||
|     #         str(template), | ||||
|     #         str(dest / f"{st}.ipynb"), | ||||
|     #         parameters=dict(tribe=tribe, student=st, source=str(tribe_dir.absolute())), | ||||
|     #     ) | ||||
|  | ||||
|   | ||||
| @@ -1,69 +1,4 @@ | ||||
| ansiwrap==0.8.4 | ||||
| attrs==19.1.0 | ||||
| backcall==0.1.0 | ||||
| bleach==3.1.0 | ||||
| certifi==2019.6.16 | ||||
| chardet==3.0.4 | ||||
| Click==7.0 | ||||
| colorama==0.4.1 | ||||
| cycler==0.10.0 | ||||
| decorator==4.4.0 | ||||
| defusedxml==0.6.0 | ||||
| entrypoints==0.3 | ||||
| future==0.17.1 | ||||
| idna==2.8 | ||||
| importlib-resources==1.0.2 | ||||
| ipykernel==5.1.1 | ||||
| ipython==7.7.0 | ||||
| ipython-genutils==0.2.0 | ||||
| ipywidgets==7.5.1 | ||||
| jedi==0.14.1 | ||||
| Jinja2==2.10.1 | ||||
| jsonschema==3.0.2 | ||||
| jupyter==1.0.0 | ||||
| jupyter-client==5.3.1 | ||||
| jupyter-console==6.0.0 | ||||
| jupyter-core==4.5.0 | ||||
| jupytex==0.0.3 | ||||
| kiwisolver==1.1.0 | ||||
| MarkupSafe==1.1.1 | ||||
| matplotlib==3.1.1 | ||||
| mistune==0.8.4 | ||||
| nbconvert==5.5.0 | ||||
| nbformat==4.4.0 | ||||
| notebook==6.0.0 | ||||
| numpy==1.17.0 | ||||
| pandas==0.25.0 | ||||
| pandocfilters==1.4.2 | ||||
| papermill==1.0.1 | ||||
| parso==0.5.1 | ||||
| pexpect==4.7.0 | ||||
| pickleshare==0.7.5 | ||||
| prometheus-client==0.7.1 | ||||
| prompt-toolkit==2.0.9 | ||||
| ptyprocess==0.6.0 | ||||
| Pygments==2.4.2 | ||||
| pyparsing==2.4.2 | ||||
| pyrsistent==0.15.4 | ||||
| python-dateutil==2.8.0 | ||||
| pytz==2019.2 | ||||
| PyYAML==5.1.2 | ||||
| pyzmq==18.0.2 | ||||
| qtconsole==4.5.2 | ||||
| -e git+git_opytex:/lafrite/recopytex.git@e9a8310f151ead60434ae944d726a2fd22b23d06#egg=Recopytex | ||||
| requests==2.22.0 | ||||
| scipy==1.3.0 | ||||
| seaborn==0.9.0 | ||||
| Send2Trash==1.5.0 | ||||
| six==1.12.0 | ||||
| tenacity==5.0.4 | ||||
| terminado==0.8.2 | ||||
| testpath==0.4.2 | ||||
| textwrap3==0.9.2 | ||||
| tornado==6.0.3 | ||||
| tqdm==4.32.2 | ||||
| traitlets==4.3.2 | ||||
| urllib3==1.25.3 | ||||
| wcwidth==0.1.7 | ||||
| webencodings==0.5.1 | ||||
| widgetsnbextension==3.5.1 | ||||
| pandas | ||||
| click | ||||
| papermill | ||||
| prompt_toolkit | ||||
|   | ||||
							
								
								
									
										69
									
								
								requirements_dev.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								requirements_dev.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| ansiwrap | ||||
| attrs | ||||
| backcall | ||||
| bleach | ||||
| certifi | ||||
| chardet | ||||
| Click | ||||
| colorama | ||||
| cycler | ||||
| decorator | ||||
| defusedxml | ||||
| entrypoints | ||||
| future | ||||
| idna | ||||
| importlib-resources | ||||
| ipykernel | ||||
| ipython | ||||
| ipython-genutils | ||||
| ipywidgets | ||||
| jedi | ||||
| Jinja2 | ||||
| jsonschema | ||||
| jupyter | ||||
| jupyter-client | ||||
| jupyter-console | ||||
| jupyter-core | ||||
| jupytex | ||||
| kiwisolver | ||||
| MarkupSafe | ||||
| matplotlib | ||||
| mistune | ||||
| nbconvert | ||||
| nbformat | ||||
| notebook | ||||
| numpy | ||||
| pandas | ||||
| pandocfilters | ||||
| papermill | ||||
| parso | ||||
| pexpect | ||||
| pickleshare | ||||
| prometheus-client | ||||
| prompt-toolkit | ||||
| ptyprocess | ||||
| Pygments | ||||
| pyparsing | ||||
| pyrsistent | ||||
| python-dateutil | ||||
| pytz | ||||
| PyYAML | ||||
| pyzmq | ||||
| qtconsole | ||||
| -e git+git_opytex:/lafrite/recopytex.git@e9a8310f151ead60434ae944d726a2fd22b23d06#egg=Recopytex | ||||
| requests | ||||
| scipy | ||||
| seaborn | ||||
| Send2Trash | ||||
| six | ||||
| tenacity | ||||
| terminado | ||||
| testpath | ||||
| textwrap3 | ||||
| tornado | ||||
| tqdm | ||||
| traitlets | ||||
| urllib3 | ||||
| wcwidth | ||||
| webencodings | ||||
| widgetsnbextension | ||||
		Reference in New Issue
	
	Block a user