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.
- bertrend/BERTopicModel.py +320 -0
- bertrend/BERTrend.py +1211 -0
- bertrend/__init__.py +59 -0
- bertrend/config/bertopic_default_config.toml +48 -0
- bertrend/config/bertrend_default_config.toml +16 -0
- bertrend/config/parameters.py +92 -0
- bertrend/config/services_default_config.toml +32 -0
- bertrend/demos/__init__.py +4 -0
- bertrend/demos/demos_utils/__init__.py +13 -0
- bertrend/demos/demos_utils/data_loading_component.py +267 -0
- bertrend/demos/demos_utils/embed_documents_component.py +52 -0
- bertrend/demos/demos_utils/icons.py +41 -0
- bertrend/demos/demos_utils/messages.py +11 -0
- bertrend/demos/demos_utils/parameters_component.py +162 -0
- bertrend/demos/demos_utils/state_utils.py +99 -0
- bertrend/demos/start_summarizer_app.sh +9 -0
- bertrend/demos/start_topic_analysis_app.sh +9 -0
- bertrend/demos/start_weak_signals_app.sh +9 -0
- bertrend/demos/summarization/__init__.py +4 -0
- bertrend/demos/summarization/summarizer_app.py +98 -0
- bertrend/demos/topic_analysis/__init__.py +4 -0
- bertrend/demos/topic_analysis/app.py +87 -0
- bertrend/demos/topic_analysis/app_utils.py +69 -0
- bertrend/demos/topic_analysis/demo_pages/__init__.py +4 -0
- bertrend/demos/topic_analysis/demo_pages/explore_topics.py +455 -0
- bertrend/demos/topic_analysis/demo_pages/newsletters_generation.py +178 -0
- bertrend/demos/topic_analysis/demo_pages/temporal_visualizations.py +573 -0
- bertrend/demos/topic_analysis/demo_pages/topic_visualizations.py +280 -0
- bertrend/demos/topic_analysis/demo_pages/training_page.py +219 -0
- bertrend/demos/topic_analysis/messages.py +13 -0
- bertrend/demos/weak_signals/__init__.py +4 -0
- bertrend/demos/weak_signals/app.py +510 -0
- bertrend/demos/weak_signals/messages.py +32 -0
- bertrend/demos/weak_signals/visualizations_utils.py +403 -0
- bertrend/llm_utils/__init__.py +4 -0
- bertrend/llm_utils/newsletter.css +131 -0
- bertrend/llm_utils/newsletter_features.py +272 -0
- bertrend/llm_utils/openai_client.py +235 -0
- bertrend/llm_utils/prompts.py +94 -0
- bertrend/metrics/__init__.py +4 -0
- bertrend/metrics/temporal_metrics_embedding.py +861 -0
- bertrend/metrics/topic_metrics.py +199 -0
- bertrend/resources/common_ngrams.json +23 -0
- bertrend/resources/stopwords-en.json +3 -0
- bertrend/resources/stopwords-fr.json +1 -0
- bertrend/resources/stopwords-rte.json +15 -0
- bertrend/services/__init__.py +4 -0
- bertrend/services/authentication.py +66 -0
- bertrend/services/embedding_client.py +154 -0
- bertrend/services/embedding_service.py +380 -0
- bertrend/services/summarizer.py +45 -0
- bertrend/services/summary/__init__.py +4 -0
- bertrend/services/summary/abstractive_summarizer.py +74 -0
- bertrend/services/summary/chatgpt_summarizer.py +67 -0
- bertrend/services/summary/extractive_summarizer.py +368 -0
- bertrend/services/summary/lexrank.py +126 -0
- bertrend/services/summary/prompts.py +43 -0
- bertrend/tests/__init__.py +4 -0
- bertrend/tests/test_abstractive_summarizer.py +140 -0
- bertrend/tests/test_bertrend.py +1091 -0
- bertrend/tests/test_cache_utils.py +111 -0
- bertrend/tests/test_chatgpt_summarizer.py +150 -0
- bertrend/tests/test_config_utils.py +54 -0
- bertrend/tests/test_data_loading.py +326 -0
- bertrend/tests/test_data_structure.py +53 -0
- bertrend/tests/test_embedding_client.py +546 -0
- bertrend/tests/test_extractive_summarizer.py +250 -0
- bertrend/tests/test_installation.py +183 -0
- bertrend/tests/test_newsletter_features.py +354 -0
- bertrend/tests/test_openai_client.py +210 -0
- bertrend/tests/test_representative_docs.py +125 -0
- bertrend/tests/test_summarizer.py +115 -0
- bertrend/tests/test_temporal_metrics_embedding.py +349 -0
- bertrend/tests/test_topic_description.py +159 -0
- bertrend/tests/test_topic_metrics.py +233 -0
- bertrend/tests/test_topic_model.py +285 -0
- bertrend/tests/test_trend_analysis_data_structure.py +174 -0
- bertrend/tests/test_trend_analysis_prompts.py +225 -0
- bertrend/tests/test_trend_analysis_visualizations.py +307 -0
- bertrend/tests/test_trend_analysis_weak_signals.py +598 -0
- bertrend/tests/test_utils_init.py +70 -0
- bertrend/tests/test_visualizations.py +306 -0
- bertrend/topic_analysis/__init__.py +4 -0
- bertrend/topic_analysis/data_structure.py +13 -0
- bertrend/topic_analysis/prompts.py +42 -0
- bertrend/topic_analysis/representative_docs.py +57 -0
- bertrend/topic_analysis/topic_description.py +66 -0
- bertrend/topic_analysis/visualizations.py +315 -0
- bertrend/trend_analysis/__init__.py +4 -0
- bertrend/trend_analysis/data_structure.py +78 -0
- bertrend/trend_analysis/prompts.py +229 -0
- bertrend/trend_analysis/signal_llm_template_en.html +181 -0
- bertrend/trend_analysis/signal_llm_template_fr.html +181 -0
- bertrend/trend_analysis/visualizations.py +605 -0
- bertrend/trend_analysis/weak_signals.py +425 -0
- bertrend/utils/__init__.py +31 -0
- bertrend/utils/cache_utils.py +31 -0
- bertrend/utils/config_utils.py +41 -0
- bertrend/utils/data_loading.py +329 -0
- bertrend-0.3.1.dist-info/AUTHORS.txt +15 -0
- bertrend-0.3.1.dist-info/LICENSE.md +373 -0
- bertrend-0.3.1.dist-info/METADATA +400 -0
- bertrend-0.3.1.dist-info/RECORD +181 -0
- bertrend-0.3.1.dist-info/WHEEL +4 -0
- bertrend_apps/__init__.py +4 -0
- bertrend_apps/common/__init__.py +4 -0
- bertrend_apps/common/crontab_utils.py +113 -0
- bertrend_apps/common/mail_utils.py +91 -0
- bertrend_apps/config/feeds/arxiv_feed.toml +15 -0
- bertrend_apps/config/feeds/decarbonation_feed.toml +15 -0
- bertrend_apps/config/feeds/llm_en_feed.toml +15 -0
- bertrend_apps/config/feeds/llm_feed.toml +16 -0
- bertrend_apps/config/feeds/nlp_feed.toml +15 -0
- bertrend_apps/config/feeds/rte_feed.toml +15 -0
- bertrend_apps/config/gmail_credentials.json +1 -0
- bertrend_apps/config/newsletters/arxiv_newsletter_from_last.toml +39 -0
- bertrend_apps/config/newsletters/arxiv_newsletter_from_scratch.toml +39 -0
- bertrend_apps/config/newsletters/decarbonation_newsletter_from_last.toml +38 -0
- bertrend_apps/config/newsletters/decarbonation_newsletter_from_scratch.toml +38 -0
- bertrend_apps/config/newsletters/llm_en_newsletter_from_last.toml +38 -0
- bertrend_apps/config/newsletters/llm_newsletter_from_last.toml +39 -0
- bertrend_apps/config/newsletters/nlp_newsletter_from_last.toml +38 -0
- bertrend_apps/config/newsletters/rte_newsletter_from_last.toml +38 -0
- bertrend_apps/config/newsletters/rte_newsletter_from_scratch.toml +38 -0
- bertrend_apps/data_provider/__init__.py +9 -0
- bertrend_apps/data_provider/__main__.py +237 -0
- bertrend_apps/data_provider/arxiv_provider.py +161 -0
- bertrend_apps/data_provider/bing_news_provider.py +82 -0
- bertrend_apps/data_provider/curebot_provider.py +105 -0
- bertrend_apps/data_provider/data_provider.py +194 -0
- bertrend_apps/data_provider/google_news_provider.py +94 -0
- bertrend_apps/data_provider/newscatcher_provider.py +74 -0
- bertrend_apps/data_provider/utils.py +165 -0
- bertrend_apps/exploration/__init__.py +4 -0
- bertrend_apps/exploration/curebot/__init__.py +4 -0
- bertrend_apps/exploration/curebot/app.py +45 -0
- bertrend_apps/exploration/curebot/app_utils.py +317 -0
- bertrend_apps/exploration/curebot/config.toml +33 -0
- bertrend_apps/exploration/curebot/import_extraction_article.ipynb +108 -0
- bertrend_apps/exploration/curebot/old_veille_analyse.py +298 -0
- bertrend_apps/exploration/curebot/prompts.py +24 -0
- bertrend_apps/exploration/curebot/start_newsletter_generator.sh +8 -0
- bertrend_apps/exploration/curebot/tabs/tab1.py +150 -0
- bertrend_apps/exploration/curebot/tabs/tab2.py +95 -0
- bertrend_apps/exploration/curebot/tabs/tab3.py +125 -0
- bertrend_apps/exploration/dataviz/Main_page.py +10 -0
- bertrend_apps/exploration/dataviz/__init__.py +4 -0
- bertrend_apps/exploration/dataviz/pages/1_Data_feeds.py +105 -0
- bertrend_apps/exploration/geolocalization/__init__.py +4 -0
- bertrend_apps/exploration/geolocalization/entity_utils.py +118 -0
- bertrend_apps/exploration/geolocalization/spacy/__init__.py +4 -0
- bertrend_apps/exploration/geolocalization/spacy/common_factory.py +142 -0
- bertrend_apps/exploration/geolocalization/spacy/dbpedia.py +206 -0
- bertrend_apps/exploration/geolocalization/spacy/nlp.config +37 -0
- bertrend_apps/exploration/geolocalization/spacy/utils.py +39 -0
- bertrend_apps/install_feeds_and_newsletters.py +29 -0
- bertrend_apps/newsletters/__init__.py +4 -0
- bertrend_apps/newsletters/__main__.py +277 -0
- bertrend_apps/prospective_demo/.streamlit/config.toml +3 -0
- bertrend_apps/prospective_demo/.streamlit/secrets.toml +9 -0
- bertrend_apps/prospective_demo/__init__.py +70 -0
- bertrend_apps/prospective_demo/app.py +111 -0
- bertrend_apps/prospective_demo/authentication.py +44 -0
- bertrend_apps/prospective_demo/dashboard_analysis.py +132 -0
- bertrend_apps/prospective_demo/dashboard_common.py +79 -0
- bertrend_apps/prospective_demo/dashboard_signals.py +226 -0
- bertrend_apps/prospective_demo/feeds_common.py +38 -0
- bertrend_apps/prospective_demo/feeds_config.py +261 -0
- bertrend_apps/prospective_demo/feeds_data.py +95 -0
- bertrend_apps/prospective_demo/i18n.py +136 -0
- bertrend_apps/prospective_demo/llm_utils.py +34 -0
- bertrend_apps/prospective_demo/models_info.py +361 -0
- bertrend_apps/prospective_demo/perf_utils.py +23 -0
- bertrend_apps/prospective_demo/process_new_data.py +348 -0
- bertrend_apps/prospective_demo/report_generation.py +214 -0
- bertrend_apps/prospective_demo/report_template.html +148 -0
- bertrend_apps/prospective_demo/report_utils.py +132 -0
- bertrend_apps/prospective_demo/start_demo.sh +9 -0
- bertrend_apps/prospective_demo/streamlit_utils.py +37 -0
- bertrend_apps/start_apps.sh +15 -0
- 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
|