diff --git a/app/odi_streamlit.py b/app/odi_streamlit.py index a93d896..8354167 100644 --- a/app/odi_streamlit.py +++ b/app/odi_streamlit.py @@ -36,9 +36,10 @@ def load_data(file: str) -> pd.DataFrame: return df # TODO connect to Google Sheet and load data -file_path = ("https://raw.githubusercontent.com/dataforgoodfr/12_observatoire_des_imaginaires/" - "main/data/Etape%201%20Identification%20du%20film%20-%20Feuille%201%20-%20enrichi.csv") - +file_path = ("https://raw.githubusercontent.com/dataforgoodfr/" +"12_observatoire_des_imaginaires/analyse/streamlit_app_v2/" +"data/Etape%201%20Identification%20du%20film%20-%20Feuille%201%20-%20enrichi.csv") +#"./data/Etape 1 Identification du film - Feuille 1 - enrichi.csv" if "data" not in st.session_state: st.session_state["data"] = load_data(file_path) @@ -49,6 +50,17 @@ def load_data(file: str) -> pd.DataFrame: # Renommer la colonne title -> TITRE data.rename(columns={"title": "TITRE"}, inplace=True) +# Et Supprimer les lignes où toutes les valeurs sont NaN +df = data.dropna(how="all") + +# Nettoyage du data set + +# mettre les titres en majuscule +df["TITRE"] = df["TITRE"].str.upper() +# Convertir les années en entier +annee = "release_year" +df[annee] = pd.to_numeric(df[annee], errors="coerce").fillna(0).astype(int) + logo= ("https://media.licdn.com/dms/image/D4E0BAQEZHVBxFn3OXQ/company-logo_200_200/" "0/1697116934909/cercle_thmatique_culture_the_shifters_logo?e=1718841600&v=beta" "&t=_2DWaEBrblIgXhgVASUipHTcJesOL6s1Sk2uH73Kx58") @@ -65,6 +77,24 @@ def load_data(file: str) -> pd.DataFrame: "Cette application analyse les données du sondage " "de **l'Observatoire des Imaginaires**. ", ) + with st.container(border=True): + st.markdown("**Filtres d'analyse**") + + #filtre année de sortie + list_year = df["release_year"].sort_values().unique() + start_clr, end_clr = st.select_slider("Choisissez la/les date(s) d'analyse", + options=list_year, value=(min(list_year), max(list_year))) + + #filtre type de contenu vu + type_contenu_choice = st.selectbox( + "Choisissez un type de contenu", + df["TYPE"].unique(), + index=None, + ) + + #filtre nom de l'oeuvre + choix_nom_oeuvre = st.multiselect("Choisissez un ou plusieurs film(s)", + df["TITRE"].sort_values().unique(), default=None) ### B. Container du header @@ -75,17 +105,6 @@ def load_data(file: str) -> pd.DataFrame: ### C. Container des métriques cont_metric = st.container() -# Et Supprimer les lignes où toutes les valeurs sont NaN -df = data.dropna(how="all") - -# Nettoyage du data set - -# mettre les titres en majuscule -df["TITRE"] = df["TITRE"].str.upper() -# Convertir les années en entier -annee = "release_year" -df[annee] = pd.to_numeric(df[annee], errors="coerce").fillna(0).astype(int) - with cont_metric: with st.expander("Aperçu des données"): st.dataframe(df) @@ -204,6 +223,7 @@ def load_data(file: str) -> pd.DataFrame: col_pays, col_contenu_vide, col_pays_annee = st.columns([4, 0.5, 4]) country_group_df = df + country_group_df = ( country_group_df.groupby("pays_rework") .count() @@ -269,6 +289,7 @@ def load_data(file: str) -> pd.DataFrame: ) st.plotly_chart(fig_pays, use_container_width=True) + with col_pays_annee: country_group_df_rework = df.groupby(["pays_rework", "production_countries", @@ -380,6 +401,7 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: st.plotly_chart(fig, theme="streamlit") + # Préparation du dataframe pour les films genre_group_df = df[["id_tmdb", "genres", "TITRE", "TYPE"]].drop_duplicates() @@ -408,18 +430,68 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: get_chart_82052330(total_film, liste_genre_cine, "Répartition des genres (uniques)") +### Analyses des sociétés de productions with st.container(): + st.subheader("Sociétés de production") + + col_choix_film_prod, col_prod_graph = st.columns([3, 5]) + # Préparation du dataframe pour les films - productions_df = df[ - ["id_tmdb", "TITRE", "TYPE", "production_companies"] - ].drop_duplicates() + if len(choix_nom_oeuvre)!=0 and type_contenu_choice is not None: + productions_df = df[(df["TITRE"].isin(choix_nom_oeuvre))&\ + (df["TYPE"]== type_contenu_choice)&\ + (df.release_year >= start_clr) & \ + (df.release_year <= end_clr)][ + ["id_tmdb", "TITRE", "TYPE", "production_companies"] + ].drop_duplicates() + if len(productions_df) == 0: + productions_df = df[ + ["id_tmdb", "TITRE", "TYPE", "production_companies"] + ].drop_duplicates() + with col_choix_film_prod: + st.markdown("#### :warning: Aucun film ne correspond à votre choix.\ + Veuillez modifier un ou plusieurs filtres", + ) + elif len(choix_nom_oeuvre)!=0 and type_contenu_choice is None: + productions_df = df[(df["TITRE"].isin(choix_nom_oeuvre))&\ + (df.release_year >= start_clr) & \ + (df.release_year <= end_clr)][ + ["id_tmdb", "TITRE", "TYPE", "production_companies"] + ].drop_duplicates() + if len(productions_df) == 0: + productions_df = df[ + ["id_tmdb", "TITRE", "TYPE", "production_companies"] + ].drop_duplicates() + with col_choix_film_prod: + st.markdown("#### :warning: Aucun film ne correspond à votre choix.\ + Veuillez modifier un ou plusieurs filtres", + ) + elif len(choix_nom_oeuvre)==0 and type_contenu_choice is not None: + productions_df = df[(df["TYPE"] == (type_contenu_choice))&\ + (df.release_year >= start_clr) & \ + (df.release_year <= end_clr)][ + ["id_tmdb", "TITRE", "TYPE", "production_companies"] + ].drop_duplicates() + if len(productions_df) == 0: + productions_df = df[ + ["id_tmdb", "TITRE", "TYPE", "production_companies"] + ].drop_duplicates() + with col_choix_film_prod: + st.markdown("#### :warning: Aucun film ne correspond à votre choix.\ + Veuillez modifier un ou plusieurs filtres", + ) + else: + productions_df = df[(df.release_year >= start_clr) & \ + (df.release_year <= end_clr)][ + ["id_tmdb", "TITRE", "TYPE", "production_companies"] + ].drop_duplicates() + # je crée une liste de genres uniques liste_production_cine = list({p for prod in productions_df["production_companies"] for p in prod.split(",") }) - # je compte le nombre de films par producteur productions_df = pd.concat( [productions_df, pd.DataFrame(columns=liste_production_cine)], @@ -443,21 +515,34 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: .reset_index() .rename(columns={0: "total_film", "index": "production_companies"}) ) - total_film_prod.insert(2, "TYPE", "FILM") - get_chart_82052330( + with col_choix_film_prod: + # Combine all parts and use markdown + prod_comment = ( + f"**Répartition des sociétes de productions** " + f"pour :blue[{type_contenu_choice}] ont pour " + f"pays d'origine :blue[{choix_nom_oeuvre}] (:blue[%]), alors que la " + f"majorité des contenus français sont visionnés xxx (xxx%)." + ) + st.markdown(prod_comment) + with col_prod_graph: + get_chart_82052330( total_film_prod, liste_production_cine, "Répartition des producteurs", - ) + ) with st.container(): st.subheader("Récompenses") - col_choix_annee, col_award_graph = st.columns([4, 6]) - # Préparation du dataframe pour les films - award_df = df[ - ["id_tmdb", "TITRE", "TYPE", "nb_recompense", "liste_festival","release_year"] - ].drop_duplicates() + if len(choix_nom_oeuvre)==0: + award_df = df[ + ["id_tmdb", "TITRE", "TYPE", "nb_recompense", "liste_festival","release_year"] + ].drop_duplicates() + st.dataframe(award_df) + else : + award_df = df[df["TITRE"].isin(choix_nom_oeuvre)][ + ["id_tmdb", "TITRE", "TYPE", "nb_recompense", "liste_festival","release_year"] + ].drop_duplicates() liste_award_cine = list({p for prod in award_df["liste_festival"] for p in str(prod).split(",") @@ -465,43 +550,56 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: if (debug): st.write(liste_award_cine) - list_year = award_df["release_year"].sort_values().unique() - - with col_choix_annee : - start_clr, end_clr = st.select_slider("Choisir la/les date(s) d'analyse", - options=list_year, value=(min(list_year), max(list_year))) - - # je conmpte le nombre de films par récompense award_df = pd.concat([award_df, pd.DataFrame(columns=liste_award_cine)]) for col in liste_award_cine: award_df[col] = [1 if col in str(a).split(",")\ else 0 for a in award_df["liste_festival"]] - # j'ajoute une colonne qui fait la somme des films pour une récompense donnés - # et ajoute le type pour cette nouvelle ligne - total_film_award = dict( - award_df.loc[(award_df["TYPE"] == "FILM")&\ + #J'isole le nombre de contenus sans récompenses + + content_without_award = len(award_df[(award_df["liste_festival"].isnull())&\ (award_df.release_year >= start_clr) & \ - (award_df.release_year <= end_clr)][liste_award_cine].sum(), + (award_df.release_year <= end_clr)])\ + /len(award_df[(award_df.release_year >= start_clr) & \ + (award_df.release_year <= end_clr)]) + + content_without_award_string = ( + f"**:blue[{int(content_without_award*100)}%]** des oeuvres analyseés " + f"n'ont reçu aucun prix cinématographique." ) + # j'ajoute une colonne qui fait la somme des films pour une récompense donnés + # et ajoute le type pour cette nouvelle ligne + if type_choice is None: + total_film_award = dict( + award_df.loc[(award_df["liste_festival"].notnull())&\ + (award_df.release_year >= start_clr) & \ + (award_df.release_year <= end_clr)][liste_award_cine].sum(), + ) + else : + st.write(award_df) + total_film_award = dict( + award_df[(award_df["liste_festival"].notnull())&\ + (award_df.release_year >= start_clr) &\ + (award_df.release_year <= end_clr)][liste_award_cine].sum(), + ) + total_film_award = ( pd.DataFrame.from_dict(total_film_award, orient="index") .reset_index() - .rename(columns={0: "total_film", "index": "liste_festival"}) - ) - total_film_award.insert(2, "TYPE", "FILM") + .rename(columns={0: "total_film", "index": "liste_festival"})) - with col_award_graph : - get_chart_82052330( - total_film_award, liste_award_cine, - f"Répartition des récompenses de {start_clr} à {end_clr}") + get_chart_82052330( + total_film_award, liste_award_cine, + f"Répartition des récompenses de {start_clr} à {end_clr}") + + st.write(content_without_award_string) st.divider() -### TODO Analyse de l’échantillon +### TODO Analyse de l'échantillon # Questions # Quels sont les sous-échantillons statistiquement représentatifs qui peuvent être analysés ? @@ -517,9 +615,9 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # Répartition des genres (uniques) - ok # Répartition des producteurs - déjà ok # Nombre de films pour chaque type de récompense documentées -# (Césars, Cannes, Oscars…) / par année de sortie +# (Césars, Cannes, Oscars…) / par année de sortie - ok # Année de sortie en fonction de nationalité -# Genres en fonction de l’année de sortie +# Genres en fonction de l'année de sortie # Genres en fonction de la nationalité # Nationalité en fonction du canal de diffusion # Genre en fonction du canal de diffusion @@ -528,11 +626,11 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # TODO Analyse des doublons # Pour chaque contenu présents plusieurs fois: # visualisation de toutes les réponses divergentes -# visualisation des personnages à la même désignation (nom ou nom d’acteur) +# visualisation des personnages à la même désignation (nom ou nom d'acteur) # et des réponses divergentes pour les mêmes personnages -# TODO Analyse de l’arène +# TODO Analyse de l'arène # Questions # Où se passent les récits ? @@ -540,7 +638,7 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # Dans quels types de société se déroulent nos récits (réalité vs fantaisie, dystopie # vs utopies…) ? Y a t-il une influence du genre ? # À quelle époque se passent les récits ? Quelle est la proportion de récits qui ne se -# déroulent pas à l’époque de leur écriture ? Comment est-ce influencé par leur genre ? +# déroulent pas à l'époque de leur écriture ? Comment est-ce influencé par leur genre ? # Est-ce que ces tendances évoluent au cours du temps ? st.subheader("Analyse de l'époque du récit") @@ -650,15 +748,15 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # Visualisations # Répartitions: -# Pays de l’action +# Pays de l'action # Nombre de pays par film (1, 2 …) -# Environnement de l’action -# Époque de l’action -# Temporalité de l’action (i.e. temps de l’action par rapport à époque d’écriture du récit) +# Environnement de l'action +# Époque de l'action +# Temporalité de l'action (i.e. temps de l'action par rapport à époque d'écriture du récit) # Type de société # Type de mondes # Corrélations : -# Pays de l’action vs pays de production +# Pays de l'action vs pays de production # Type de monde vs année de production # Type de monde vs genre # Type de monde vs nationalité du film @@ -672,14 +770,14 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # Temporalité du récit vs canal de diffusion # Temporalité du récit vs type de monde # Temporalité du récit vs type de société -# Nombre de pays de l’action vs genre +# Nombre de pays de l'action vs genre # TODO Analyse des personnages renseignés # Questions: # Quelles sont les caractéristiques des personnages ? Qui sont-ils ? Comment vivent-ils ? -# Quelle est l’influence des caractéristiques du film sur les caractéristiques des personnages ? +# Quelle est l'influence des caractéristiques du film sur les caractéristiques des personnages ? # Visualisations: # Nombre total de personnages renseignés @@ -687,7 +785,7 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # En cas de contenus identiques, identification des désignations identiques et # comparaison des divergences dans les répon # Répartitions: -# Tranches d’âges +# Tranches d'âges # Genre # Ethnicités # Gentil ou méchant @@ -701,7 +799,7 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # Questions # Les personnages de fiction présentent-ils des traits de caractères écologiques ? -# si oui, qui sont ces personnages ? Est-ce que c’est influencé par les caractéristiques +# si oui, qui sont ces personnages ? Est-ce que c'est influencé par les caractéristiques # du film (nationalité …) ? Est-ce que ça évolue dans le temps ? # Visualisations @@ -711,31 +809,31 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # Corrélation entre la présence de personnage ayant une sensibilité écolo et les # caractéristiques du film (année / nationalité / genre / producteur / canal de diffusion) -# TODO Analyse de la mobilité à l’écran +# TODO Analyse de la mobilité à l'écran # Questions -# Comment se déplace-t-on à l’écran ? Est-ce qu’il y a une corrélation entre -# Visualisation de la proportion de modes de transport représentés à l’écran. Filtres +# Comment se déplace-t-on à l'écran ? Est-ce qu'il y a une corrélation entre +# Visualisation de la proportion de modes de transport représentés à l'écran. Filtres # possibles sur les caractéristiques du contenu (ex. que les films français) -# ou sur la nature des personnages (ex. tranches d’âge). +# ou sur la nature des personnages (ex. tranches d'âge). # Objectif: répondre aux questions suivantes: -# Comment se déplace-t-on à l’écran ? -# Est-ce que ça varie selon le type de personnage et leur sensibilité à l’écologie ? +# Comment se déplace-t-on à l'écran ? +# Est-ce que ça varie selon le type de personnage et leur sensibilité à l'écologie ? -# TODO Analyse de l’habitat -# Visualisation générale des modes d’habitat à l’écran, avec filtres possibles sur les +# TODO Analyse de l'habitat +# Visualisation générale des modes d'habitat à l'écran, avec filtres possibles sur les # types de contenu ou sur les caractéristiques des personnages (ex. comment habitent les -# jeunes ? comment habitent les CSP+ ?). Importance corréler l’habitat à l’emploi exercé +# jeunes ? comment habitent les CSP+ ?). Importance corréler l'habitat à l'emploi exercé # (i.e. la catégorie socio-professionnelle). -# Corrélation entre les lieux de vie et les lieux de l’action (dans la catégorie arène). +# Corrélation entre les lieux de vie et les lieux de l'action (dans la catégorie arène). # Question posée : les “aventures” se passent-elles forcément loin du lieu de vie des -# personnages ? Regarder notamment l’influence du genre et l’influence de la nationalité +# personnages ? Regarder notamment l'influence du genre et l'influence de la nationalité # du film -# TODO Analyse de l’emploi -# Visualisation des emplois représentés à l’écran selon le type de contenu. -# Intéressant de regarder qui pratique quel type d’emploi (femmes vs hommes, jeunes…) -# Corrélation entre le métier pratiqué et la sensibilité du personnage à l’écologie +# TODO Analyse de l'emploi +# Visualisation des emplois représentés à l'écran selon le type de contenu. +# Intéressant de regarder qui pratique quel type d'emploi (femmes vs hommes, jeunes…) +# Corrélation entre le métier pratiqué et la sensibilité du personnage à l'écologie with st.container(): st.subheader("Analyse des métiers") @@ -813,9 +911,9 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # Analyse de la technologie -# Visualisation de l’emploi de la technologie à l’écran selon le type de film (regarder en -# particulier le genre) et le type de personnage (corréler en particulier à l’âge). -# Question sous-jacente : comment utilise-t-on la technologie à l’écran ? est-ce +# Visualisation de l'emploi de la technologie à l'écran selon le type de film (regarder en +# particulier le genre) et le type de personnage (corréler en particulier à l'âge). +# Question sous-jacente : comment utilise-t-on la technologie à l'écran ? est-ce # systématique ? est-ce corrélé à une certaine forme de réalité des usages ? with st.container(): st.subheader("Analyse de la technologie") @@ -903,6 +1001,7 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: melted_data_all = prepare_technology_data(data=df, colname_id="age_group") melted_data_all.rename(columns={"age_group": "Catégorie d'âge"}, inplace=True) + fig = px.histogram( melted_data_all, x="Technology", @@ -925,14 +1024,14 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # TODO Analyse des modes de vie -# TODO L’écologie dans le récit +# TODO L'écologie dans le récit # TODO # Pédagogie? # Cartographie des contenus qui mentionnent un enjeu écologique et corrélation à leurs # caractéristiques (nationalité etc.). Est-ce que ça a évolué au cours du temps ? Est-ce -# que certains genres s’y prêtent plus que d’autres ? Quand l’écologie est mentionnée, -# de quel type de récit s’agit-il ? (dystopie, récit futuriste…) +# que certains genres s'y prêtent plus que d'autres ? Quand l'écologie est mentionnée, +# de quel type de récit s'agit-il ? (dystopie, récit futuriste…) # Adéquation entre le score calculé et le score proposé par les répondants # Enjeux écologiques les plus fréquemment montrés / les plus ignorés @@ -992,28 +1091,28 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: st.plotly_chart(fig) -# Box office / récompenses obtenues par les films qui parlent d’écologie ou qui ont +# Box office / récompenses obtenues par les films qui parlent d'écologie ou qui ont # des scores écologiques élevées (question : ces films sont-ils vus ?) -# A l’inverse, quels scores écologiques pour les films les plus vus au box office ? +# A l'inverse, quels scores écologiques pour les films les plus vus au box office ? # Pédagogie clandestine ? # Visualisation et statistiques sur les comportements listés, avec filtres possibles # sur la nature des contenus. # Corrélation au score écologique proposé par les répondants, la question étant : les -# spectateurs font-ils le lien entre certains comportements montrés à l’écran et -# l’impact écologique d’un contenu ? +# spectateurs font-ils le lien entre certains comportements montrés à l'écran et +# l'impact écologique d'un contenu ? # Personnage écolo vs récit écolo -# La biodiversité à l’écran +# La biodiversité à l'écran # La perception des répondants # Quels types de contenus remportent les meilleurs scores? # Quels types de contenus remportent les moins bons scores? -# L’influence du profil des répondants +# L'influence du profil des répondants # Sur les scores fournis # Sur le nombre de réponses type “je ne sais pas / je ne me souviens plus” @@ -1021,7 +1120,7 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # Questions: # Quelles sont les caractéristiques des personnages ? Qui sont-ils ? Comment vivent-ils ? -# Quelle est l’influence des caractéristiques du film sur les caractéristiques des personnages ? +# Quelle est l'influence des caractéristiques du film sur les caractéristiques des personnages ? # Visualisations: # Nombre total de personnages renseignés @@ -1038,7 +1137,7 @@ def get_chart_82052330(df: pd.DataFrame, liste: list[str], titre: str) -> None: # comparaison des divergences dans les répon # Répartitions: -# Tranches d’âges +# Tranches d'âges # Melanger les differentes characteres blended_column = [val for pair in zip(data["character2_age_group"], data["character4_age_group"], strict=False) for val in pair] diff --git a/app/pages/odi - enrichissement fichier.py b/app/pages/odi - enrichissement fichier.py index 4dc8753..abaf71c 100644 --- a/app/pages/odi - enrichissement fichier.py +++ b/app/pages/odi - enrichissement fichier.py @@ -51,7 +51,7 @@ @st.cache_data def load_header_tmdb(): with open("./secret/TMDB.txt", "r") as file: - file_lines = file.read().split('\n') + file_lines = file.read().split('\n') header_tmdb = [token.split(':') for token in file_lines][0][1].replace('/n','') return header_tmdb diff --git a/poetry.lock b/poetry.lock index 9db7c24..1919ece 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2878,6 +2878,21 @@ files = [ [package.dependencies] streamlit = ">=0.63" +[[package]] +name = "streamlit-plotly-events" +version = "0.0.6" +description = "Plotly chart component for Streamlit that also allows for events to bubble back up to Streamlit." +optional = false +python-versions = ">=3.6" +files = [ + {file = "streamlit-plotly-events-0.0.6.tar.gz", hash = "sha256:1fe25dbf0e5d803aeb90253be04d7b395f5bcfdf3c654f96ff3c19424e7f9582"}, + {file = "streamlit_plotly_events-0.0.6-py3-none-any.whl", hash = "sha256:e63fbe3c6a0746fdfce20060fc45ba5cd97805505c332b27372dcbd02c2ede29"}, +] + +[package.dependencies] +plotly = ">=4.14.3" +streamlit = ">=0.63" + [[package]] name = "tenacity" version = "8.3.0" @@ -3370,4 +3385,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "234f5bae8df2d725b09e251697514ba125026d24693c3267f53b9e8d45429e92" +content-hash = "c91b8b5bf0ecd42b5935d7208155127fb4d7cb8a6a1900d41c6ab2dd2e0833fb" diff --git a/pyproject.toml b/pyproject.toml index 447573d..8c2e529 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ python-dotenv = "^1.0.1" datasets = "^2.18.0" pycountry-convert = "^0.7.2" fire = "^0.6.0" +streamlit-plotly-events = "^0.0.6" [tool.poetry.group.dev.dependencies] pre-commit = "^2.20.0"