bertrend 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. bertrend/BERTopicModel.py +320 -0
  2. bertrend/BERTrend.py +1211 -0
  3. bertrend/__init__.py +59 -0
  4. bertrend/config/bertopic_default_config.toml +48 -0
  5. bertrend/config/bertrend_default_config.toml +16 -0
  6. bertrend/config/parameters.py +92 -0
  7. bertrend/config/services_default_config.toml +32 -0
  8. bertrend/demos/__init__.py +4 -0
  9. bertrend/demos/demos_utils/__init__.py +13 -0
  10. bertrend/demos/demos_utils/data_loading_component.py +267 -0
  11. bertrend/demos/demos_utils/embed_documents_component.py +52 -0
  12. bertrend/demos/demos_utils/icons.py +41 -0
  13. bertrend/demos/demos_utils/messages.py +11 -0
  14. bertrend/demos/demos_utils/parameters_component.py +162 -0
  15. bertrend/demos/demos_utils/state_utils.py +99 -0
  16. bertrend/demos/start_summarizer_app.sh +9 -0
  17. bertrend/demos/start_topic_analysis_app.sh +9 -0
  18. bertrend/demos/start_weak_signals_app.sh +9 -0
  19. bertrend/demos/summarization/__init__.py +4 -0
  20. bertrend/demos/summarization/summarizer_app.py +98 -0
  21. bertrend/demos/topic_analysis/__init__.py +4 -0
  22. bertrend/demos/topic_analysis/app.py +87 -0
  23. bertrend/demos/topic_analysis/app_utils.py +69 -0
  24. bertrend/demos/topic_analysis/demo_pages/__init__.py +4 -0
  25. bertrend/demos/topic_analysis/demo_pages/explore_topics.py +455 -0
  26. bertrend/demos/topic_analysis/demo_pages/newsletters_generation.py +178 -0
  27. bertrend/demos/topic_analysis/demo_pages/temporal_visualizations.py +573 -0
  28. bertrend/demos/topic_analysis/demo_pages/topic_visualizations.py +280 -0
  29. bertrend/demos/topic_analysis/demo_pages/training_page.py +219 -0
  30. bertrend/demos/topic_analysis/messages.py +13 -0
  31. bertrend/demos/weak_signals/__init__.py +4 -0
  32. bertrend/demos/weak_signals/app.py +510 -0
  33. bertrend/demos/weak_signals/messages.py +32 -0
  34. bertrend/demos/weak_signals/visualizations_utils.py +403 -0
  35. bertrend/llm_utils/__init__.py +4 -0
  36. bertrend/llm_utils/newsletter.css +131 -0
  37. bertrend/llm_utils/newsletter_features.py +272 -0
  38. bertrend/llm_utils/openai_client.py +235 -0
  39. bertrend/llm_utils/prompts.py +94 -0
  40. bertrend/metrics/__init__.py +4 -0
  41. bertrend/metrics/temporal_metrics_embedding.py +861 -0
  42. bertrend/metrics/topic_metrics.py +199 -0
  43. bertrend/resources/common_ngrams.json +23 -0
  44. bertrend/resources/stopwords-en.json +3 -0
  45. bertrend/resources/stopwords-fr.json +1 -0
  46. bertrend/resources/stopwords-rte.json +15 -0
  47. bertrend/services/__init__.py +4 -0
  48. bertrend/services/authentication.py +66 -0
  49. bertrend/services/embedding_client.py +154 -0
  50. bertrend/services/embedding_service.py +380 -0
  51. bertrend/services/summarizer.py +45 -0
  52. bertrend/services/summary/__init__.py +4 -0
  53. bertrend/services/summary/abstractive_summarizer.py +74 -0
  54. bertrend/services/summary/chatgpt_summarizer.py +67 -0
  55. bertrend/services/summary/extractive_summarizer.py +368 -0
  56. bertrend/services/summary/lexrank.py +126 -0
  57. bertrend/services/summary/prompts.py +43 -0
  58. bertrend/tests/__init__.py +4 -0
  59. bertrend/tests/test_abstractive_summarizer.py +140 -0
  60. bertrend/tests/test_bertrend.py +1091 -0
  61. bertrend/tests/test_cache_utils.py +111 -0
  62. bertrend/tests/test_chatgpt_summarizer.py +150 -0
  63. bertrend/tests/test_config_utils.py +54 -0
  64. bertrend/tests/test_data_loading.py +326 -0
  65. bertrend/tests/test_data_structure.py +53 -0
  66. bertrend/tests/test_embedding_client.py +546 -0
  67. bertrend/tests/test_extractive_summarizer.py +250 -0
  68. bertrend/tests/test_installation.py +183 -0
  69. bertrend/tests/test_newsletter_features.py +354 -0
  70. bertrend/tests/test_openai_client.py +210 -0
  71. bertrend/tests/test_representative_docs.py +125 -0
  72. bertrend/tests/test_summarizer.py +115 -0
  73. bertrend/tests/test_temporal_metrics_embedding.py +349 -0
  74. bertrend/tests/test_topic_description.py +159 -0
  75. bertrend/tests/test_topic_metrics.py +233 -0
  76. bertrend/tests/test_topic_model.py +285 -0
  77. bertrend/tests/test_trend_analysis_data_structure.py +174 -0
  78. bertrend/tests/test_trend_analysis_prompts.py +225 -0
  79. bertrend/tests/test_trend_analysis_visualizations.py +307 -0
  80. bertrend/tests/test_trend_analysis_weak_signals.py +598 -0
  81. bertrend/tests/test_utils_init.py +70 -0
  82. bertrend/tests/test_visualizations.py +306 -0
  83. bertrend/topic_analysis/__init__.py +4 -0
  84. bertrend/topic_analysis/data_structure.py +13 -0
  85. bertrend/topic_analysis/prompts.py +42 -0
  86. bertrend/topic_analysis/representative_docs.py +57 -0
  87. bertrend/topic_analysis/topic_description.py +66 -0
  88. bertrend/topic_analysis/visualizations.py +315 -0
  89. bertrend/trend_analysis/__init__.py +4 -0
  90. bertrend/trend_analysis/data_structure.py +78 -0
  91. bertrend/trend_analysis/prompts.py +229 -0
  92. bertrend/trend_analysis/signal_llm_template_en.html +181 -0
  93. bertrend/trend_analysis/signal_llm_template_fr.html +181 -0
  94. bertrend/trend_analysis/visualizations.py +605 -0
  95. bertrend/trend_analysis/weak_signals.py +425 -0
  96. bertrend/utils/__init__.py +31 -0
  97. bertrend/utils/cache_utils.py +31 -0
  98. bertrend/utils/config_utils.py +41 -0
  99. bertrend/utils/data_loading.py +329 -0
  100. bertrend-0.3.1.dist-info/AUTHORS.txt +15 -0
  101. bertrend-0.3.1.dist-info/LICENSE.md +373 -0
  102. bertrend-0.3.1.dist-info/METADATA +400 -0
  103. bertrend-0.3.1.dist-info/RECORD +181 -0
  104. bertrend-0.3.1.dist-info/WHEEL +4 -0
  105. bertrend_apps/__init__.py +4 -0
  106. bertrend_apps/common/__init__.py +4 -0
  107. bertrend_apps/common/crontab_utils.py +113 -0
  108. bertrend_apps/common/mail_utils.py +91 -0
  109. bertrend_apps/config/feeds/arxiv_feed.toml +15 -0
  110. bertrend_apps/config/feeds/decarbonation_feed.toml +15 -0
  111. bertrend_apps/config/feeds/llm_en_feed.toml +15 -0
  112. bertrend_apps/config/feeds/llm_feed.toml +16 -0
  113. bertrend_apps/config/feeds/nlp_feed.toml +15 -0
  114. bertrend_apps/config/feeds/rte_feed.toml +15 -0
  115. bertrend_apps/config/gmail_credentials.json +1 -0
  116. bertrend_apps/config/newsletters/arxiv_newsletter_from_last.toml +39 -0
  117. bertrend_apps/config/newsletters/arxiv_newsletter_from_scratch.toml +39 -0
  118. bertrend_apps/config/newsletters/decarbonation_newsletter_from_last.toml +38 -0
  119. bertrend_apps/config/newsletters/decarbonation_newsletter_from_scratch.toml +38 -0
  120. bertrend_apps/config/newsletters/llm_en_newsletter_from_last.toml +38 -0
  121. bertrend_apps/config/newsletters/llm_newsletter_from_last.toml +39 -0
  122. bertrend_apps/config/newsletters/nlp_newsletter_from_last.toml +38 -0
  123. bertrend_apps/config/newsletters/rte_newsletter_from_last.toml +38 -0
  124. bertrend_apps/config/newsletters/rte_newsletter_from_scratch.toml +38 -0
  125. bertrend_apps/data_provider/__init__.py +9 -0
  126. bertrend_apps/data_provider/__main__.py +237 -0
  127. bertrend_apps/data_provider/arxiv_provider.py +161 -0
  128. bertrend_apps/data_provider/bing_news_provider.py +82 -0
  129. bertrend_apps/data_provider/curebot_provider.py +105 -0
  130. bertrend_apps/data_provider/data_provider.py +194 -0
  131. bertrend_apps/data_provider/google_news_provider.py +94 -0
  132. bertrend_apps/data_provider/newscatcher_provider.py +74 -0
  133. bertrend_apps/data_provider/utils.py +165 -0
  134. bertrend_apps/exploration/__init__.py +4 -0
  135. bertrend_apps/exploration/curebot/__init__.py +4 -0
  136. bertrend_apps/exploration/curebot/app.py +45 -0
  137. bertrend_apps/exploration/curebot/app_utils.py +317 -0
  138. bertrend_apps/exploration/curebot/config.toml +33 -0
  139. bertrend_apps/exploration/curebot/import_extraction_article.ipynb +108 -0
  140. bertrend_apps/exploration/curebot/old_veille_analyse.py +298 -0
  141. bertrend_apps/exploration/curebot/prompts.py +24 -0
  142. bertrend_apps/exploration/curebot/start_newsletter_generator.sh +8 -0
  143. bertrend_apps/exploration/curebot/tabs/tab1.py +150 -0
  144. bertrend_apps/exploration/curebot/tabs/tab2.py +95 -0
  145. bertrend_apps/exploration/curebot/tabs/tab3.py +125 -0
  146. bertrend_apps/exploration/dataviz/Main_page.py +10 -0
  147. bertrend_apps/exploration/dataviz/__init__.py +4 -0
  148. bertrend_apps/exploration/dataviz/pages/1_Data_feeds.py +105 -0
  149. bertrend_apps/exploration/geolocalization/__init__.py +4 -0
  150. bertrend_apps/exploration/geolocalization/entity_utils.py +118 -0
  151. bertrend_apps/exploration/geolocalization/spacy/__init__.py +4 -0
  152. bertrend_apps/exploration/geolocalization/spacy/common_factory.py +142 -0
  153. bertrend_apps/exploration/geolocalization/spacy/dbpedia.py +206 -0
  154. bertrend_apps/exploration/geolocalization/spacy/nlp.config +37 -0
  155. bertrend_apps/exploration/geolocalization/spacy/utils.py +39 -0
  156. bertrend_apps/install_feeds_and_newsletters.py +29 -0
  157. bertrend_apps/newsletters/__init__.py +4 -0
  158. bertrend_apps/newsletters/__main__.py +277 -0
  159. bertrend_apps/prospective_demo/.streamlit/config.toml +3 -0
  160. bertrend_apps/prospective_demo/.streamlit/secrets.toml +9 -0
  161. bertrend_apps/prospective_demo/__init__.py +70 -0
  162. bertrend_apps/prospective_demo/app.py +111 -0
  163. bertrend_apps/prospective_demo/authentication.py +44 -0
  164. bertrend_apps/prospective_demo/dashboard_analysis.py +132 -0
  165. bertrend_apps/prospective_demo/dashboard_common.py +79 -0
  166. bertrend_apps/prospective_demo/dashboard_signals.py +226 -0
  167. bertrend_apps/prospective_demo/feeds_common.py +38 -0
  168. bertrend_apps/prospective_demo/feeds_config.py +261 -0
  169. bertrend_apps/prospective_demo/feeds_data.py +95 -0
  170. bertrend_apps/prospective_demo/i18n.py +136 -0
  171. bertrend_apps/prospective_demo/llm_utils.py +34 -0
  172. bertrend_apps/prospective_demo/models_info.py +361 -0
  173. bertrend_apps/prospective_demo/perf_utils.py +23 -0
  174. bertrend_apps/prospective_demo/process_new_data.py +348 -0
  175. bertrend_apps/prospective_demo/report_generation.py +214 -0
  176. bertrend_apps/prospective_demo/report_template.html +148 -0
  177. bertrend_apps/prospective_demo/report_utils.py +132 -0
  178. bertrend_apps/prospective_demo/start_demo.sh +9 -0
  179. bertrend_apps/prospective_demo/streamlit_utils.py +37 -0
  180. bertrend_apps/start_apps.sh +15 -0
  181. bertrend_apps/stop_apps.sh +11 -0
@@ -0,0 +1,136 @@
1
+ """
2
+ Internationalization (i18n) module for the prospective demo application.
3
+ Provides functionality for translating text between French and English.
4
+ """
5
+
6
+ # Copyright (c) 2024, RTE (https://www.rte-france.com)
7
+ # See AUTHORS.txt
8
+ # SPDX-License-Identifier: MPL-2.0
9
+ # This file is part of BERTrend.
10
+
11
+ import streamlit as st
12
+ from typing import Dict, Any, Optional
13
+
14
+ # Available languages
15
+ LANGUAGES = {"fr": "Français", "en": "English"}
16
+
17
+ # Default language
18
+ DEFAULT_LANGUAGE = "fr"
19
+
20
+ # Translation dictionaries
21
+ TRANSLATIONS = {
22
+ # App.py translations
23
+ "app_title": {
24
+ "fr": "BERTrend - Démo Veille & Analyse",
25
+ "en": "BERTrend - Monitoring & Analysis Demo",
26
+ },
27
+ "tab_monitoring": {"fr": "Veilles", "en": "Monitoring"},
28
+ "tab_models": {"fr": "Modèles", "en": "Models"},
29
+ "tab_trends": {"fr": "Tendances", "en": "Trends"},
30
+ "tab_analysis": {"fr": "Analyses", "en": "Analysis"},
31
+ "tab_reports": {"fr": "Génération de rapports", "en": "Report Generation"},
32
+ "data_flow_config": {
33
+ "fr": "Configuration des flux de données",
34
+ "en": "Data Flow Configuration",
35
+ },
36
+ "data_collection_status": {
37
+ "fr": "Etat de collecte des données",
38
+ "en": "Data Collection Status",
39
+ },
40
+ "model_status_by_monitoring": {
41
+ "fr": "Statut des modèles par veille",
42
+ "en": "Model Status by Monitoring",
43
+ },
44
+ # dashboard_analysis.py translations
45
+ "detailed_analysis_by_topic": {
46
+ "fr": "Analyse détaillée par sujet",
47
+ "en": "Detailed Analysis by Topic",
48
+ },
49
+ "topic_selection": {"fr": "Sélection du sujet", "en": "Topic Selection"},
50
+ "emerging_topic": {"fr": "Sujet émergent", "en": "Emerging Topic"},
51
+ "strong_topic": {"fr": "Sujet fort", "en": "Strong Topic"},
52
+ "nothing_to_display": {"fr": "Rien à afficher", "en": "Nothing to display"},
53
+ # dashboard_signals.py translations
54
+ "title": {"fr": "Titre", "en": "Title"},
55
+ "todo_message": {
56
+ "fr": "TODO <Place disponible pour d'autres infos...>En particulier, rajouter la courbe d'évolution des sujets en se limitant à ceux présentés dans les tableaux weak/strong/noise",
57
+ "en": "TODO <Space available for other information...>In particular, add the topic evolution curve limited to those presented in the weak/strong/noise tables",
58
+ },
59
+ "explore_sources_by_topic": {
60
+ "fr": "Exploration des sources par sujet",
61
+ "en": "Explore Sources by Topic",
62
+ },
63
+ "signal_type": {"fr": "Type de signal", "en": "Signal Type"},
64
+ "emerging_topics": {"fr": "Sujets émergents", "en": "Emerging Topics"},
65
+ "strong_topics": {"fr": "Sujets forts", "en": "Strong Topics"},
66
+ "no_data": {"fr": "Pas de données", "en": "No data"},
67
+ "topic": {"fr": "Sujet", "en": "Topic"},
68
+ "explore_sources": {"fr": "Exploration des sources", "en": "Explore Sources"},
69
+ "reference_articles": {"fr": "Articles de référence", "en": "Reference Articles"},
70
+ # Signal categories
71
+ "weak_signals": {"fr": "Signaux faibles", "en": "Weak Signals"},
72
+ "strong_signals": {"fr": "Signaux forts", "en": "Strong Signals"},
73
+ "noise": {"fr": "Bruit", "en": "Noise"},
74
+ "no_weak_signals": {
75
+ "fr": "Aucun signal faible n'a été détecté à l'horodatage {timestamp}.",
76
+ "en": "No weak signals were detected at timestamp {timestamp}.",
77
+ },
78
+ "no_strong_signals": {
79
+ "fr": "Aucun signal fort n'a été détecté à l'horodatage {timestamp}.",
80
+ "en": "No strong signals were detected at timestamp {timestamp}.",
81
+ },
82
+ "no_noise_signals": {
83
+ "fr": "Aucun signal de bruit n'a été détecté à l'horodatage {timestamp}.",
84
+ "en": "No noisy signals were detected at timestamp {timestamp}.",
85
+ },
86
+ }
87
+
88
+
89
+ def get_current_language() -> str:
90
+ """Get the currently selected language from the session state."""
91
+ if "language" not in st.session_state:
92
+ st.session_state.language = DEFAULT_LANGUAGE
93
+ return st.session_state.language
94
+
95
+
96
+ def set_language(language: str) -> None:
97
+ """Set the current language in the session state."""
98
+ if language in LANGUAGES:
99
+ st.session_state.language = language
100
+
101
+
102
+ def create_language_selector() -> None:
103
+ """Create a language selector widget in the sidebar."""
104
+ current_lang = get_current_language()
105
+ selected_lang = st.sidebar.selectbox(
106
+ "Language / Langue",
107
+ options=list(LANGUAGES.keys()),
108
+ format_func=lambda x: LANGUAGES[x],
109
+ index=list(LANGUAGES.keys()).index(current_lang),
110
+ )
111
+
112
+ if selected_lang != current_lang:
113
+ set_language(selected_lang)
114
+ st.rerun()
115
+
116
+
117
+ def translate(key: str, default: Optional[str] = None) -> str:
118
+ """
119
+ Translate a text key to the current language.
120
+
121
+ Args:
122
+ key: The translation key to look up
123
+ default: Default text to return if the key is not found
124
+
125
+ Returns:
126
+ The translated text in the current language
127
+ """
128
+ lang = get_current_language()
129
+
130
+ if key in TRANSLATIONS and lang in TRANSLATIONS[key]:
131
+ return TRANSLATIONS[key][lang]
132
+
133
+ # If the key doesn't exist or the language is not available for this key
134
+ if default:
135
+ return default
136
+ return key # Return the key itself as fallback
@@ -0,0 +1,34 @@
1
+ # Copyright (c) 2024, RTE (https://www.rte-france.com)
2
+ # See AUTHORS.txt
3
+ # SPDX-License-Identifier: MPL-2.0
4
+ # This file is part of BERTrend.
5
+ from loguru import logger
6
+
7
+ from bertrend.topic_analysis.data_structure import TopicDescription
8
+ from bertrend.topic_analysis.topic_description import get_topic_description
9
+
10
+
11
+ def generate_bertrend_topic_description(
12
+ topic_words: str,
13
+ topic_number: int,
14
+ texts: list[str],
15
+ language_code: str = "fr",
16
+ ) -> tuple[str, str]:
17
+ """Generates a LLM-based human-readable description of a topic composed of a title and a description (as a dict)"""
18
+ if not texts:
19
+ logger.warning(f"No text found for topic number {topic_number}")
20
+ return None
21
+
22
+ topic_representation = ", ".join(topic_words.split("_")) # Get top 10 words
23
+
24
+ # Prepare the documents text
25
+ docs_text = "\n\n".join(
26
+ [f"Document {i + 1}: {doc[0:2000]}..." for i, doc in enumerate(texts)]
27
+ )
28
+
29
+ topic_description: TopicDescription = get_topic_description(
30
+ topic_representation, docs_text, language_code
31
+ )
32
+ if not topic_description:
33
+ return None, None
34
+ return topic_description.title, topic_description.description
@@ -0,0 +1,361 @@
1
+ # Copyright (c) 2024, RTE (https://www.rte-france.com)
2
+ # See AUTHORS.txt
3
+ # SPDX-License-Identifier: MPL-2.0
4
+ # This file is part of BERTrend.
5
+ import multiprocessing
6
+ import os
7
+ import random
8
+ import shutil
9
+ import sys
10
+ import time
11
+
12
+ import pandas as pd
13
+ import streamlit as st
14
+ import toml
15
+ from loguru import logger
16
+
17
+ from bertrend import BERTREND_LOG_PATH, BEST_CUDA_DEVICE, load_toml_config
18
+ from bertrend.demos.demos_utils.icons import (
19
+ EDIT_ICON,
20
+ DELETE_ICON,
21
+ WARNING_ICON,
22
+ INFO_ICON,
23
+ TOGGLE_ON_ICON,
24
+ TOGGLE_OFF_ICON,
25
+ ERROR_ICON,
26
+ RESTART_ICON,
27
+ )
28
+ from bertrend_apps.common.crontab_utils import (
29
+ check_cron_job,
30
+ remove_from_crontab,
31
+ add_job_to_crontab,
32
+ )
33
+ from bertrend_apps.prospective_demo import (
34
+ get_user_models_path,
35
+ get_model_cfg_path,
36
+ DEFAULT_ANALYSIS_CFG,
37
+ )
38
+ from bertrend_apps.prospective_demo.perf_utils import get_least_used_gpu
39
+ from bertrend_apps.prospective_demo.process_new_data import regenerate_models
40
+ from bertrend_apps.prospective_demo.streamlit_utils import clickable_df
41
+
42
+
43
+ @st.fragment
44
+ def models_monitoring():
45
+ if not st.session_state.user_feeds:
46
+ st.stop()
47
+
48
+ st.session_state.model_analysis_cfg = {}
49
+ displayed_list = []
50
+
51
+ for model_id in sorted(st.session_state.user_feeds.keys()):
52
+ try:
53
+ st.session_state.model_analysis_cfg[model_id] = load_toml_config(
54
+ get_model_cfg_path(
55
+ user_name=st.session_state.username, model_id=model_id
56
+ )
57
+ )
58
+ except Exception:
59
+ st.session_state.model_analysis_cfg[model_id] = DEFAULT_ANALYSIS_CFG
60
+ list_models = get_models_info(model_id)
61
+ displayed_list.append(
62
+ {
63
+ "id": model_id,
64
+ "# modèles": len(list_models) if list_models else 0,
65
+ "date 1er modèle": list_models[0] if list_models else None,
66
+ "date dernier modèle": list_models[-1] if list_models else None,
67
+ "fréquence mise à jour (# jours)": st.session_state.model_analysis_cfg[
68
+ model_id
69
+ ]["model_config"]["granularity"],
70
+ "fenêtre d'analyse (# jours)": st.session_state.model_analysis_cfg[
71
+ model_id
72
+ ]["model_config"]["window_size"],
73
+ }
74
+ )
75
+
76
+ st.session_state.models_paths = {
77
+ model_id: get_user_models_path(st.session_state.username, model_id)
78
+ for model_id in st.session_state.user_feeds.keys()
79
+ }
80
+
81
+ df = pd.DataFrame(displayed_list)
82
+ if not df.empty:
83
+ df = df.sort_values(by="id", inplace=False).reset_index(drop=True)
84
+
85
+ clickable_df_buttons = [
86
+ (EDIT_ICON, edit_model_parameters, "secondary"),
87
+ (lambda x: toggle_icon(df, x), toggle_learning, "secondary"),
88
+ (DELETE_ICON, handle_delete_models, "primary"),
89
+ (RESTART_ICON, handle_regenerate_models, "primary"),
90
+ ]
91
+ clickable_df(df, clickable_df_buttons)
92
+
93
+
94
+ @st.dialog("Paramètres")
95
+ def edit_model_parameters(row_dict: dict):
96
+ model_id = row_dict["id"]
97
+ st.write(f"**Paramètres des modèles pour la veille {model_id}**")
98
+
99
+ new_granularity = st.slider(
100
+ "Fréquence de mise à jour des modèles (en jours)",
101
+ min_value=1,
102
+ max_value=30,
103
+ value=st.session_state.model_analysis_cfg[model_id]["model_config"][
104
+ "granularity"
105
+ ],
106
+ step=1,
107
+ help=f"{INFO_ICON} Sélection de la fréquence à laquelle la détection de sujets est effectuée. "
108
+ f"Le nombre de jours sélectionné doit être choisi pour s'assurer d'un volume de données suffisant.",
109
+ )
110
+ new_window_size = st.slider(
111
+ "Sélection de la fenêtre temporelle (en jours)",
112
+ min_value=new_granularity,
113
+ max_value=30,
114
+ value=max(
115
+ st.session_state.model_analysis_cfg[model_id]["model_config"][
116
+ "window_size"
117
+ ],
118
+ new_granularity,
119
+ ),
120
+ step=1,
121
+ help=f"{INFO_ICON} Sélection de la plage temporelle considérée pour calculer les différents "
122
+ f"types de signaux (faibles, forts)",
123
+ )
124
+
125
+ st.write(f"**Paramètres d'analyse de la veille {model_id}: éléments à inclure**")
126
+ topic_evolution = st.checkbox(
127
+ "Evolution du sujet",
128
+ value=st.session_state.model_analysis_cfg[model_id]["analysis_config"][
129
+ "topic_evolution"
130
+ ],
131
+ )
132
+ evolution_scenarios = st.checkbox(
133
+ "Scénarios d'évolution",
134
+ value=st.session_state.model_analysis_cfg[model_id]["analysis_config"][
135
+ "evolution_scenarios"
136
+ ],
137
+ )
138
+ multifactorial_analysis = st.checkbox(
139
+ "Analyse multifactorielle",
140
+ value=st.session_state.model_analysis_cfg[model_id]["analysis_config"][
141
+ "multifactorial_analysis"
142
+ ],
143
+ )
144
+
145
+ model_config = {
146
+ "granularity": new_granularity,
147
+ "window_size": new_window_size,
148
+ "language": (
149
+ "French"
150
+ if st.session_state.user_feeds[model_id]["data-feed"]["language"] == "fr"
151
+ else "English"
152
+ ),
153
+ }
154
+ analysis_config = {
155
+ "topic_evolution": topic_evolution,
156
+ "evolution_scenarios": evolution_scenarios,
157
+ "multifactorial_analysis": multifactorial_analysis,
158
+ }
159
+
160
+ if st.button("OK"):
161
+ save_model_config(
162
+ model_id, {"model_config": model_config, "analysis_config": analysis_config}
163
+ )
164
+ st.rerun()
165
+
166
+
167
+ def save_model_config(model_id: str, config: dict):
168
+ model_cfg_path = get_model_cfg_path(
169
+ user_name=st.session_state.username, model_id=model_id
170
+ )
171
+ with open(model_cfg_path, "w") as toml_file:
172
+ toml.dump(config, toml_file)
173
+ logger.debug(f"Saved model analysis config {config} to {model_cfg_path}")
174
+
175
+
176
+ def load_model_config(model_id: str) -> dict:
177
+ model_cfg_path = get_model_cfg_path(
178
+ user_name=st.session_state.username, model_id=model_id
179
+ )
180
+ try:
181
+ return load_toml_config(model_cfg_path)
182
+ except Exception:
183
+ return DEFAULT_ANALYSIS_CFG
184
+
185
+
186
+ @st.dialog("Confirmation")
187
+ def handle_delete_models(row_dict: dict):
188
+ """Function to handle remove models from cache"""
189
+ model_id = row_dict["id"]
190
+ st.warning(
191
+ f":orange[{WARNING_ICON}] Voulez-vous vraiment supprimer tous les modèles stockés pour la veille **{model_id}** ?"
192
+ )
193
+ col1, col2, _ = st.columns([2, 2, 8])
194
+ with col1:
195
+ if st.button("Oui", type="primary"):
196
+ remove_scheduled_training_for_user(
197
+ model_id=model_id, user=st.session_state.username
198
+ )
199
+ delete_cached_models(model_id)
200
+ logger.info(f"Modèles en cache supprimés pour la veille {model_id} !")
201
+ time.sleep(0.2)
202
+ st.rerun()
203
+ with col2:
204
+ if st.button("Non"):
205
+ st.rerun()
206
+
207
+
208
+ @st.dialog("Regénération des modèles")
209
+ def handle_regenerate_models(row_dict: dict):
210
+ """Function to regenerate models from scratch"""
211
+ model_id = row_dict["id"]
212
+ st.warning(
213
+ f"{WARNING_ICON} Voulez-vous re-générer l'ensemble des modèles pour la veille {model_id} ?"
214
+ )
215
+ st.warning(
216
+ f"{WARNING_ICON} L'ensemble des modèles existant pour cette veille sera supprimé."
217
+ )
218
+ st.error(
219
+ f"{ERROR_ICON} Attention, cette regénération ne peut pas être annulée une fois lancée !"
220
+ )
221
+ col1, col2, _ = st.columns([2, 2, 8])
222
+ with col1:
223
+ if yes_btn := st.button("Oui", type="primary"):
224
+ # Delete previously stored model
225
+ delete_cached_models(model_id)
226
+ logger.info(f"Modèles en cache supprimés pour la veille {model_id} !")
227
+
228
+ # Regenerate new models
229
+ # Launch model generation in a separate thread to avoid blocking the app
230
+ # Create a new process with parameters
231
+ # Set the start method to 'spawn' - required for a process using CUDA
232
+ os.environ["CUDA_VISIBLE_DEVICES"] = get_least_used_gpu()
233
+ multiprocessing.set_start_method("spawn", force=True)
234
+
235
+ process = multiprocessing.Process(
236
+ target=regenerate_models,
237
+ kwargs={"model_id": model_id, "user": st.session_state.username},
238
+ )
239
+ # Start the process
240
+ process.start()
241
+
242
+ time.sleep(0.2)
243
+ # st.rerun()
244
+
245
+ with col2:
246
+ if st.button("Non"):
247
+ st.rerun()
248
+
249
+ if yes_btn:
250
+ st.info(
251
+ f"{INFO_ICON} Regénération en cours des modèles pour la veille {model_id}. "
252
+ "L'opération peut prendre un peu de temps."
253
+ )
254
+ st.info(f"{INFO_ICON} Vous pouvez fermer cette fenêtre.")
255
+
256
+
257
+ def toggle_learning(cfg: dict):
258
+ """Activate / deactivate the learning from the crontab"""
259
+ model_id = cfg["id"]
260
+ if check_if_learning_active_for_user(
261
+ model_id=model_id, user=st.session_state.username
262
+ ):
263
+ if remove_scheduled_training_for_user(
264
+ model_id=model_id, user=st.session_state.username
265
+ ):
266
+ st.toast(
267
+ f"Le learning pour la veille **{model_id}** est déactivé !",
268
+ icon=INFO_ICON,
269
+ )
270
+ logger.info(f"Learning pour {model_id} désactivé !")
271
+ else:
272
+ schedule_training_for_user(model_id, st.session_state.username)
273
+ st.toast(
274
+ f"Le learning pour la veille **{model_id}** est activé !", icon=WARNING_ICON
275
+ )
276
+ logger.info(f"Learning pour {model_id} activé !")
277
+ time.sleep(0.2)
278
+ st.rerun()
279
+
280
+
281
+ def toggle_icon(df: pd.DataFrame, index: int) -> str:
282
+ """Switch the toggle icon depending on the statis of the scrapping feed in the crontab"""
283
+ model_id = df["id"][index]
284
+ return (
285
+ f":green[{TOGGLE_ON_ICON}]"
286
+ if check_if_learning_active_for_user(
287
+ model_id=model_id, user=st.session_state.username
288
+ )
289
+ else f":red[{TOGGLE_OFF_ICON}]"
290
+ )
291
+
292
+
293
+ def check_if_learning_active_for_user(model_id: str, user: str):
294
+ """Checks if a given scrapping feed is active (registered in the crontab"""
295
+ if user:
296
+ return check_cron_job(rf"process_new_data train-new-model.*{user}.*{model_id}")
297
+ else:
298
+ return False
299
+
300
+
301
+ def remove_scheduled_training_for_user(model_id: str, user: str):
302
+ """Removes from the crontab the training job matching the provided model_id"""
303
+ if user:
304
+ return remove_from_crontab(
305
+ rf"process_new_data train-new-model.*{user}.*{model_id}"
306
+ )
307
+
308
+
309
+ def schedule_training_for_user(model_id: str, user: str):
310
+ """Schedule data scrapping on the basis of a feed configuration file"""
311
+ schedule = generate_crontab_expression(
312
+ st.session_state.model_analysis_cfg[model_id]["model_config"]["granularity"]
313
+ )
314
+ logpath = BERTREND_LOG_PATH / "users" / user
315
+ logpath.mkdir(parents=True, exist_ok=True)
316
+ command = (
317
+ f"{sys.prefix}/bin/python -m bertrend_apps.prospective_demo.process_new_data train-new-model {user} {model_id} "
318
+ f"> {logpath}/learning_{model_id}.log 2>&1"
319
+ )
320
+ env_vars = f"CUDA_VISIBLE_DEVICES={BEST_CUDA_DEVICE}"
321
+ add_job_to_crontab(schedule, command, env_vars)
322
+
323
+
324
+ def delete_cached_models(model_id: str):
325
+ """Removes models from the cache"""
326
+ # Remove the directory and all its contents
327
+ shutil.rmtree(st.session_state.models_paths[model_id])
328
+
329
+
330
+ def generate_crontab_expression(days_interval: int) -> str:
331
+ # Random hour between 0 and 6 (inclusive)
332
+ hour = random.randint(0, 6) # run during the night
333
+ # Random minute rounded to the nearest 10
334
+ minute = random.choice([0, 10, 20, 30, 40, 50])
335
+ # Compute days
336
+ days = [str(i) for i in range(1, 31, days_interval)]
337
+ # Crontab expression format: minute hour day_of_month month day_of_week
338
+ crontab_expression = f"{minute} {hour} {','.join(days)} * *"
339
+ return crontab_expression
340
+
341
+
342
+ def safe_timestamp(x: str) -> pd.Timestamp | None:
343
+ try:
344
+ return pd.Timestamp(x)
345
+ except Exception as e:
346
+ return None
347
+
348
+
349
+ def get_models_info(model_id: str) -> list:
350
+ """Returns the list of topic models that are stored, identified by their timestamp"""
351
+ user_model_dir = get_user_models_path(st.session_state.username, model_id)
352
+ if not user_model_dir.exists():
353
+ return []
354
+ matching_files = user_model_dir.glob(r"????-??-??")
355
+ return sorted(
356
+ [
357
+ safe_timestamp(x.name)
358
+ for x in matching_files
359
+ if safe_timestamp(x.name) is not None
360
+ ]
361
+ )
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2024, RTE (https://www.rte-france.com)
2
+ # See AUTHORS.txt
3
+ # SPDX-License-Identifier: MPL-2.0
4
+ # This file is part of BERTrend.
5
+ import subprocess
6
+
7
+
8
+ def get_least_used_gpu():
9
+ try:
10
+ # Execute the command
11
+ result = subprocess.run(
12
+ "nvidia-smi --query-gpu=index,memory.used --format=csv,nounits | tail -n +2 | sort -t',' -k2 -n | head -n 1 | cut -d',' -f1",
13
+ shell=True,
14
+ check=True,
15
+ text=True,
16
+ capture_output=True,
17
+ )
18
+ # Get the output
19
+ least_used_gpu = result.stdout.strip()
20
+ return least_used_gpu
21
+ except subprocess.CalledProcessError as e:
22
+ print(f"An error occurred: {e}")
23
+ return None