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,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,4 @@
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.
@@ -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
+ }