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,403 @@
|
|
|
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
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import streamlit as st
|
|
8
|
+
from bertopic import BERTopic
|
|
9
|
+
from pandas import Timestamp
|
|
10
|
+
from plotly import graph_objects as go
|
|
11
|
+
|
|
12
|
+
from bertrend import SIGNAL_EVOLUTION_DATA_DIR
|
|
13
|
+
from bertrend.demos.demos_utils.icons import (
|
|
14
|
+
SUCCESS_ICON,
|
|
15
|
+
INFO_ICON,
|
|
16
|
+
STRONG_SIGNAL_ICON,
|
|
17
|
+
WEAK_SIGNAL_ICON,
|
|
18
|
+
NOISE_ICON,
|
|
19
|
+
)
|
|
20
|
+
from bertrend.demos.demos_utils.state_utils import SessionStateManager
|
|
21
|
+
from bertrend.config.parameters import (
|
|
22
|
+
MAX_WINDOW_SIZE,
|
|
23
|
+
DEFAULT_WINDOW_SIZE,
|
|
24
|
+
INDIVIDUAL_MODEL_TOPIC_COUNTS_FILE,
|
|
25
|
+
CUMULATIVE_MERGED_TOPIC_COUNTS_FILE,
|
|
26
|
+
)
|
|
27
|
+
from bertrend.trend_analysis.prompts import fill_html_template
|
|
28
|
+
from bertrend.trend_analysis.visualizations import (
|
|
29
|
+
create_sankey_diagram_plotly,
|
|
30
|
+
plot_newly_emerged_topics,
|
|
31
|
+
plot_topics_for_model,
|
|
32
|
+
create_topic_size_evolution_figure,
|
|
33
|
+
plot_topic_size_evolution,
|
|
34
|
+
)
|
|
35
|
+
from bertrend.trend_analysis.weak_signals import (
|
|
36
|
+
analyze_signal,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
PLOTLY_BUTTON_SAVE_CONFIG = {
|
|
40
|
+
"toImageButtonOptions": {
|
|
41
|
+
"format": "svg",
|
|
42
|
+
# 'height': 500,
|
|
43
|
+
# 'width': 1500,
|
|
44
|
+
"scale": 1,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def display_sankey_diagram(all_merge_histories_df: pd.DataFrame) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Create a Sankey diagram to visualize the topic merging process.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
all_merge_histories_df (pd.DataFrame): The DataFrame containing all merge histories.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
go.Figure: The Plotly figure representing the Sankey diagram.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
with st.expander("Topic Merging Process", expanded=False):
|
|
61
|
+
# Create search box and slider using Streamlit
|
|
62
|
+
search_term = st.text_input("Search topics by keyword:")
|
|
63
|
+
max_pairs = st.slider(
|
|
64
|
+
"Max number of topic pairs to display",
|
|
65
|
+
min_value=1,
|
|
66
|
+
max_value=1000,
|
|
67
|
+
value=20,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Create the Sankey diagram
|
|
71
|
+
sankey_diagram = create_sankey_diagram_plotly(
|
|
72
|
+
all_merge_histories_df, search_term, max_pairs
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Display the diagram using Streamlit in an expander
|
|
76
|
+
st.plotly_chart(
|
|
77
|
+
sankey_diagram, config=PLOTLY_BUTTON_SAVE_CONFIG, use_container_width=True
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def display_signal_categories_df(
|
|
82
|
+
noise_topics_df: pd.DataFrame,
|
|
83
|
+
weak_signal_topics_df: pd.DataFrame,
|
|
84
|
+
strong_signal_topics_df: pd.DataFrame,
|
|
85
|
+
window_end: Timestamp,
|
|
86
|
+
columns=None,
|
|
87
|
+
column_order=None,
|
|
88
|
+
column_config=None,
|
|
89
|
+
):
|
|
90
|
+
"""Display the dataframes associated to each signal category: noise, weak signal, strong signal."""
|
|
91
|
+
if columns is None:
|
|
92
|
+
columns = [
|
|
93
|
+
"Topic",
|
|
94
|
+
"Sources",
|
|
95
|
+
"Source_Diversity",
|
|
96
|
+
"Representation",
|
|
97
|
+
"Latest_Popularity",
|
|
98
|
+
"Docs_Count",
|
|
99
|
+
"Paragraphs_Count",
|
|
100
|
+
"Latest_Timestamp",
|
|
101
|
+
"Documents",
|
|
102
|
+
]
|
|
103
|
+
if column_order is None:
|
|
104
|
+
column_order = columns
|
|
105
|
+
|
|
106
|
+
with st.expander(f":orange[{WEAK_SIGNAL_ICON} Weak Signals]", expanded=True):
|
|
107
|
+
st.subheader(":orange[Weak Signals]")
|
|
108
|
+
if not weak_signal_topics_df.empty:
|
|
109
|
+
displayed_df = weak_signal_topics_df[columns].sort_values(
|
|
110
|
+
by=["Latest_Popularity"], ascending=False
|
|
111
|
+
)
|
|
112
|
+
displayed_df["Documents"] = displayed_df["Documents"].astype(str)
|
|
113
|
+
st.dataframe(
|
|
114
|
+
displayed_df,
|
|
115
|
+
column_order=column_order,
|
|
116
|
+
column_config=column_config,
|
|
117
|
+
hide_index=True,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
else:
|
|
121
|
+
st.info(
|
|
122
|
+
f"No weak signals were detected at timestamp {window_end}.",
|
|
123
|
+
icon=INFO_ICON,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
with st.expander(f":green[{STRONG_SIGNAL_ICON} Strong Signals]", expanded=True):
|
|
127
|
+
st.subheader(":green[Strong Signals]")
|
|
128
|
+
if not strong_signal_topics_df.empty:
|
|
129
|
+
displayed_df = strong_signal_topics_df[columns].sort_values(
|
|
130
|
+
by=["Latest_Popularity"], ascending=False
|
|
131
|
+
)
|
|
132
|
+
displayed_df["Documents"] = displayed_df["Documents"].astype(str)
|
|
133
|
+
st.dataframe(
|
|
134
|
+
displayed_df,
|
|
135
|
+
column_order=column_order,
|
|
136
|
+
column_config=column_config,
|
|
137
|
+
hide_index=True,
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
st.info(
|
|
141
|
+
f"No strong signals were detected at timestamp {window_end}.",
|
|
142
|
+
icon=INFO_ICON,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
with st.expander(f":grey[{NOISE_ICON} Noise]", expanded=True):
|
|
146
|
+
st.subheader(":grey[Noise]")
|
|
147
|
+
if not noise_topics_df.empty:
|
|
148
|
+
displayed_df = noise_topics_df[columns].sort_values(
|
|
149
|
+
by=["Latest_Popularity"], ascending=False
|
|
150
|
+
)
|
|
151
|
+
displayed_df["Documents"] = displayed_df["Documents"].astype(str)
|
|
152
|
+
st.dataframe(
|
|
153
|
+
displayed_df,
|
|
154
|
+
column_order=column_order,
|
|
155
|
+
column_config=column_config,
|
|
156
|
+
hide_index=True,
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
st.info(
|
|
160
|
+
f"No noisy signals were detected at timestamp {window_end}.",
|
|
161
|
+
icon=INFO_ICON,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def display_popularity_evolution():
|
|
166
|
+
"""Display the popularity evolution diagram."""
|
|
167
|
+
window_size = st.number_input(
|
|
168
|
+
"Retrospective Period (days)",
|
|
169
|
+
min_value=1,
|
|
170
|
+
max_value=MAX_WINDOW_SIZE,
|
|
171
|
+
value=DEFAULT_WINDOW_SIZE,
|
|
172
|
+
key="window_size",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
bertrend = SessionStateManager.get("bertrend")
|
|
176
|
+
|
|
177
|
+
all_merge_histories_df = bertrend.all_merge_histories_df
|
|
178
|
+
min_datetime = all_merge_histories_df["Timestamp"].min().to_pydatetime()
|
|
179
|
+
max_datetime = all_merge_histories_df["Timestamp"].max().to_pydatetime()
|
|
180
|
+
|
|
181
|
+
# Get granularity
|
|
182
|
+
granularity = st.session_state["granularity"]
|
|
183
|
+
|
|
184
|
+
# Slider to select the date
|
|
185
|
+
current_date = st.slider(
|
|
186
|
+
"Current date",
|
|
187
|
+
min_value=min_datetime,
|
|
188
|
+
max_value=max_datetime,
|
|
189
|
+
step=pd.Timedelta(days=granularity),
|
|
190
|
+
format="YYYY-MM-DD",
|
|
191
|
+
help="""The earliest selectable date corresponds to the earliest timestamp when topics were merged
|
|
192
|
+
(with the smallest possible value being the earliest timestamp in the provided data).
|
|
193
|
+
The latest selectable date corresponds to the most recent topic merges, which is at most equal
|
|
194
|
+
to the latest timestamp in the data minus the provided granularity.""",
|
|
195
|
+
key="current_date",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Compute threshold values and classify signals
|
|
199
|
+
window_start, window_end, all_popularity_values, q1, q3 = (
|
|
200
|
+
bertrend._compute_popularity_values_and_thresholds(window_size, current_date)
|
|
201
|
+
)
|
|
202
|
+
st.session_state["window_start"] = window_start
|
|
203
|
+
st.session_state["window_end"] = window_end
|
|
204
|
+
st.session_state["q1"] = q1
|
|
205
|
+
st.session_state["q3"] = q3
|
|
206
|
+
|
|
207
|
+
# Display threshold values for noise and strong signals
|
|
208
|
+
col1, col2 = st.columns(2)
|
|
209
|
+
with col1:
|
|
210
|
+
st.write(f"### Noise Threshold : {'{:.3f}'.format(q1)}")
|
|
211
|
+
with col2:
|
|
212
|
+
st.write(f"### Strong Signal Threshold : {'{:.3f}'.format(q3)}")
|
|
213
|
+
|
|
214
|
+
# Plot popularity evolution with thresholds
|
|
215
|
+
fig = plot_topic_size_evolution(
|
|
216
|
+
create_topic_size_evolution_figure(bertrend.topic_sizes),
|
|
217
|
+
current_date,
|
|
218
|
+
window_start,
|
|
219
|
+
window_end,
|
|
220
|
+
all_popularity_values,
|
|
221
|
+
q1,
|
|
222
|
+
q3,
|
|
223
|
+
)
|
|
224
|
+
st.plotly_chart(fig, config=PLOTLY_BUTTON_SAVE_CONFIG, use_container_width=True)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def display_signal_types():
|
|
228
|
+
"""Show weak/strong signals"""
|
|
229
|
+
bertrend = SessionStateManager.get("bertrend")
|
|
230
|
+
|
|
231
|
+
# Classify signals
|
|
232
|
+
window_end = st.session_state.get("window_end")
|
|
233
|
+
window_start = st.session_state.get("window_start")
|
|
234
|
+
q1 = st.session_state.get("q1")
|
|
235
|
+
q3 = st.session_state.get("q3")
|
|
236
|
+
noise_topics_df, weak_signal_topics_df, strong_signal_topics_df = (
|
|
237
|
+
bertrend._classify_signals(window_start, window_end, q1, q3)
|
|
238
|
+
)
|
|
239
|
+
# Display DataFrames for each category noise, weak signals, strong signals
|
|
240
|
+
display_signal_categories_df(
|
|
241
|
+
noise_topics_df,
|
|
242
|
+
weak_signal_topics_df,
|
|
243
|
+
strong_signal_topics_df,
|
|
244
|
+
window_end,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def save_signal_evolution():
|
|
249
|
+
"""Save Signal Evolution Data to investigate later on in a separate notebook"""
|
|
250
|
+
bertrend = SessionStateManager.get("bertrend")
|
|
251
|
+
granularity = SessionStateManager.get("granularity")
|
|
252
|
+
all_merge_histories_df = bertrend.all_merge_histories_df
|
|
253
|
+
min_datetime = all_merge_histories_df["Timestamp"].min().to_pydatetime()
|
|
254
|
+
max_datetime = all_merge_histories_df["Timestamp"].max().to_pydatetime()
|
|
255
|
+
|
|
256
|
+
# Save Signal Evolution Data to investigate later on in a separate notebook
|
|
257
|
+
start_date, end_date = st.select_slider(
|
|
258
|
+
"Select date range for saving signal evolution data:",
|
|
259
|
+
options=pd.date_range(
|
|
260
|
+
start=min_datetime,
|
|
261
|
+
end=max_datetime,
|
|
262
|
+
freq=pd.Timedelta(days=granularity),
|
|
263
|
+
),
|
|
264
|
+
value=(min_datetime, max_datetime),
|
|
265
|
+
format_func=lambda x: x.strftime("%Y-%m-%d"),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if st.button("Save Signal Evolution Data"):
|
|
269
|
+
try:
|
|
270
|
+
save_path = bertrend.save_signal_evolution_data(
|
|
271
|
+
window_size=SessionStateManager.get("window_size"),
|
|
272
|
+
start_timestamp=pd.Timestamp(start_date),
|
|
273
|
+
end_timestamp=pd.Timestamp(end_date),
|
|
274
|
+
)
|
|
275
|
+
st.success(
|
|
276
|
+
f"Signal evolution data saved successfully at {save_path}",
|
|
277
|
+
icon=SUCCESS_ICON,
|
|
278
|
+
)
|
|
279
|
+
except Exception as e:
|
|
280
|
+
st.error(f"Error encountered while saving signal evolution data: {e}")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def display_newly_emerged_topics(all_new_topics_df: pd.DataFrame) -> None:
|
|
284
|
+
"""
|
|
285
|
+
Display the newly emerged topics over time (dataframe and figure).
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
all_new_topics_df (pd.DataFrame): The DataFrame containing information about newly emerged topics.
|
|
289
|
+
"""
|
|
290
|
+
fig_new_topics = plot_newly_emerged_topics(all_new_topics_df)
|
|
291
|
+
|
|
292
|
+
with st.expander("Newly Emerged Topics", expanded=False):
|
|
293
|
+
st.dataframe(
|
|
294
|
+
all_new_topics_df[
|
|
295
|
+
[
|
|
296
|
+
"Topic",
|
|
297
|
+
"Count",
|
|
298
|
+
"Document_Count",
|
|
299
|
+
"Representation",
|
|
300
|
+
"Documents",
|
|
301
|
+
"Timestamp",
|
|
302
|
+
]
|
|
303
|
+
].sort_values(by=["Timestamp", "Document_Count"], ascending=[True, False])
|
|
304
|
+
)
|
|
305
|
+
st.plotly_chart(
|
|
306
|
+
fig_new_topics, config=PLOTLY_BUTTON_SAVE_CONFIG, use_container_width=True
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def display_topics_per_timestamp(topic_models: dict[pd.Timestamp, BERTopic]) -> None:
|
|
311
|
+
"""
|
|
312
|
+
Plot the topics discussed per source for each timestamp.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
topic_models (Dict[pd.Timestamp, BERTopic]): A dictionary of BERTopic models, where the key is the timestamp
|
|
316
|
+
and the value is the corresponding model.
|
|
317
|
+
"""
|
|
318
|
+
with st.expander("Explore topic models"):
|
|
319
|
+
model_periods = sorted(topic_models.keys())
|
|
320
|
+
selected_model_period = st.select_slider(
|
|
321
|
+
"Select Model", options=model_periods, key="model_slider"
|
|
322
|
+
)
|
|
323
|
+
selected_model = topic_models[selected_model_period]
|
|
324
|
+
|
|
325
|
+
fig = plot_topics_for_model(selected_model)
|
|
326
|
+
|
|
327
|
+
st.plotly_chart(fig, config=PLOTLY_BUTTON_SAVE_CONFIG, use_container_width=True)
|
|
328
|
+
|
|
329
|
+
st.dataframe(
|
|
330
|
+
selected_model.doc_info_df[
|
|
331
|
+
["Paragraph", "document_id", "Topic", "Representation", "source"]
|
|
332
|
+
],
|
|
333
|
+
use_container_width=True,
|
|
334
|
+
)
|
|
335
|
+
st.dataframe(selected_model.topic_info_df, use_container_width=True)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def display_signal_analysis(topic_number: int):
|
|
339
|
+
"""Display a LLM-based analyis of a specific topic."""
|
|
340
|
+
bertrend = SessionStateManager.get("bertrend")
|
|
341
|
+
|
|
342
|
+
st.subheader("Signal Interpretation")
|
|
343
|
+
with st.spinner("Analyzing signal..."):
|
|
344
|
+
summaries, weak_signal_analysis = analyze_signal(
|
|
345
|
+
bertrend,
|
|
346
|
+
topic_number,
|
|
347
|
+
SessionStateManager.get("current_date"),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
formatted_html = fill_html_template(
|
|
351
|
+
summaries, weak_signal_analysis, SessionStateManager.get("language", "fr")
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Display the HTML content
|
|
355
|
+
st.html(formatted_html)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def retrieve_topic_counts(topic_models: dict[pd.Timestamp, BERTopic]) -> None:
|
|
359
|
+
individual_model_topic_counts = [
|
|
360
|
+
(timestamp, model.topic_info_df["Topic"].max() + 1)
|
|
361
|
+
for timestamp, model in topic_models.items()
|
|
362
|
+
]
|
|
363
|
+
df_individual_models = pd.DataFrame(
|
|
364
|
+
individual_model_topic_counts,
|
|
365
|
+
columns=["timestamp", "num_topics"],
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Number of topics per cumulative merged model
|
|
369
|
+
cumulative_merged_topic_counts = SessionStateManager.get(
|
|
370
|
+
"merge_df_size_over_time", []
|
|
371
|
+
)
|
|
372
|
+
df_cumulative_merged = pd.DataFrame(
|
|
373
|
+
cumulative_merged_topic_counts,
|
|
374
|
+
columns=["timestamp", "num_topics"],
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Convert to JSON
|
|
378
|
+
json_individual_models = df_individual_models.to_json(
|
|
379
|
+
orient="records", date_format="iso", indent=4
|
|
380
|
+
)
|
|
381
|
+
json_cumulative_merged = df_cumulative_merged.to_json(
|
|
382
|
+
orient="records", date_format="iso", indent=4
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# Save individual model topic counts
|
|
386
|
+
json_file_path = (
|
|
387
|
+
SIGNAL_EVOLUTION_DATA_DIR
|
|
388
|
+
/ f"retrospective_{SessionStateManager.get('window_size')}_days"
|
|
389
|
+
)
|
|
390
|
+
json_file_path.mkdir(parents=True, exist_ok=True)
|
|
391
|
+
|
|
392
|
+
(json_file_path / INDIVIDUAL_MODEL_TOPIC_COUNTS_FILE).write_text(
|
|
393
|
+
json_individual_models
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# Save cumulative merged model topic counts
|
|
397
|
+
(json_file_path / CUMULATIVE_MERGED_TOPIC_COUNTS_FILE).write_text(
|
|
398
|
+
json_cumulative_merged
|
|
399
|
+
)
|
|
400
|
+
st.success(
|
|
401
|
+
f"Topic counts for individual and cumulative merged models saved to {json_file_path}",
|
|
402
|
+
icon=SUCCESS_ICON,
|
|
403
|
+
)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024, RTE (https://www.rte-france.com)
|
|
3
|
+
* See AUTHORS.txt
|
|
4
|
+
* SPDX-License-Identifier: MPL-2.0
|
|
5
|
+
* This file is part of BERTrend.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* inspired by https://codepen.io/vedadesigns/pen/GPxYZg */
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
width:70%;
|
|
12
|
+
margin:0 auto;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
h1 {
|
|
16
|
+
position: relative;
|
|
17
|
+
padding: 0;
|
|
18
|
+
margin: 0;
|
|
19
|
+
font-family: "Raleway", sans-serif;
|
|
20
|
+
font-weight: 300;
|
|
21
|
+
font-size: 40px;
|
|
22
|
+
color: #080808;
|
|
23
|
+
-webkit-transition: all 0.4s ease 0s;
|
|
24
|
+
-o-transition: all 0.4s ease 0s;
|
|
25
|
+
transition: all 0.4s ease 0s;
|
|
26
|
+
text-align: center;
|
|
27
|
+
text-transform: uppercase;
|
|
28
|
+
padding-bottom: 5px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
h1:before {
|
|
32
|
+
width: 28px;
|
|
33
|
+
height: 5px;
|
|
34
|
+
display: block;
|
|
35
|
+
content: "";
|
|
36
|
+
position: absolute;
|
|
37
|
+
bottom: 3px;
|
|
38
|
+
left: 50%;
|
|
39
|
+
margin-left: -14px;
|
|
40
|
+
background-color: #b80000;
|
|
41
|
+
}
|
|
42
|
+
h1:after {
|
|
43
|
+
width: 100px;
|
|
44
|
+
height: 1px;
|
|
45
|
+
display: block;
|
|
46
|
+
content: "";
|
|
47
|
+
position: relative;
|
|
48
|
+
margin-top: 25px;
|
|
49
|
+
left: 50%;
|
|
50
|
+
margin-left: -50px;
|
|
51
|
+
background-color: #b80000;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
h2 {
|
|
56
|
+
padding: 0;
|
|
57
|
+
margin: 0;
|
|
58
|
+
font-family: "Raleway", sans-serif;
|
|
59
|
+
color:#c50000;
|
|
60
|
+
-webkit-transition: all 0.4s ease 0s;
|
|
61
|
+
-o-transition: all 0.4s ease 0s;
|
|
62
|
+
transition: all 0.4s ease 0s;
|
|
63
|
+
font-size: 28px;
|
|
64
|
+
font-weight: 500;
|
|
65
|
+
letter-spacing: 0;
|
|
66
|
+
line-height: 1.5em;
|
|
67
|
+
padding-bottom: 15px;
|
|
68
|
+
position: relative;
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
h2:before {
|
|
73
|
+
content: "";
|
|
74
|
+
position: absolute;
|
|
75
|
+
left: 0;
|
|
76
|
+
bottom: 0;
|
|
77
|
+
height: 5px;
|
|
78
|
+
width: 55px;
|
|
79
|
+
background-color: #111;
|
|
80
|
+
}
|
|
81
|
+
h2:after {
|
|
82
|
+
content: "";
|
|
83
|
+
position: absolute;
|
|
84
|
+
left: 0;
|
|
85
|
+
bottom: 2px;
|
|
86
|
+
height: 1px;
|
|
87
|
+
width: 95%;
|
|
88
|
+
max-width: 90%;
|
|
89
|
+
background-color: #333;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
h3 {
|
|
93
|
+
padding: 0;
|
|
94
|
+
margin: 0;
|
|
95
|
+
font-family: "Raleway", sans-serif;
|
|
96
|
+
color:DarkSlateBlue;
|
|
97
|
+
-webkit-transition: all 0.4s ease 0s;
|
|
98
|
+
-o-transition: all 0.4s ease 0s;
|
|
99
|
+
transition: all 0.4s ease 0s;
|
|
100
|
+
font-size: 18px;
|
|
101
|
+
font-weight: 500;
|
|
102
|
+
letter-spacing: 0;
|
|
103
|
+
line-height: 1.5em;
|
|
104
|
+
position: relative;
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
p { color: #222; font-family: "Raleway", sans-serif; font-size: 15px; font-weight: 400; line-height: 24px; margin: 0 0 14px; }
|
|
109
|
+
|
|
110
|
+
a { background: #fff; color: MidnightBlue; font-family: "Raleway", sans-serif; font-size: 15px; text-decoration: none; }
|
|
111
|
+
|
|
112
|
+
a:visited {
|
|
113
|
+
color: purple;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
a:hover { background: #c50000; color: #eee; text-decoration: none; }
|
|
117
|
+
|
|
118
|
+
.timestamp{
|
|
119
|
+
color: grey;
|
|
120
|
+
font-family: "Raleway", sans-serif;
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: stretch;
|
|
123
|
+
padding-bottom: 15px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.date_range{
|
|
127
|
+
color: grey;
|
|
128
|
+
font-family: "Raleway", sans-serif;
|
|
129
|
+
font-size: 20px;
|
|
130
|
+
font-weight: bold;
|
|
131
|
+
}
|