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,225 @@
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 pytest
7
+ from unittest.mock import patch, mock_open
8
+ import json
9
+
10
+ from bertrend.trend_analysis.prompts import (
11
+ get_prompt,
12
+ save_html_output,
13
+ fill_html_template,
14
+ SIGNAL_INTRO,
15
+ SIGNAL_INSTRUCTIONS,
16
+ TOPIC_SUMMARY_PROMPT,
17
+ )
18
+ from bertrend.trend_analysis.data_structure import (
19
+ TopicSummary,
20
+ TopicSummaryList,
21
+ PotentialImplications,
22
+ EvolutionScenario,
23
+ TopicInterconnexions,
24
+ Drivers,
25
+ SignalAnalysis,
26
+ )
27
+
28
+
29
+ def test_get_prompt_weak_signal():
30
+ """Test that get_prompt returns the correct weak signal prompt."""
31
+ summary_from_first_prompt = json.dumps({"test": "data"})
32
+
33
+ # Test English prompt
34
+ prompt_en = get_prompt(
35
+ language="English",
36
+ prompt_type="weak_signal",
37
+ summary_from_first_prompt=summary_from_first_prompt,
38
+ )
39
+
40
+ # Check that the prompt contains the expected parts
41
+ assert summary_from_first_prompt in prompt_en
42
+ assert (
43
+ SIGNAL_INTRO["en"].format(summary_from_first_prompt=summary_from_first_prompt)
44
+ in prompt_en
45
+ )
46
+ assert SIGNAL_INSTRUCTIONS["en"] in prompt_en
47
+
48
+ # Test French prompt
49
+ prompt_fr = get_prompt(
50
+ language="French",
51
+ prompt_type="weak_signal",
52
+ summary_from_first_prompt=summary_from_first_prompt,
53
+ )
54
+
55
+ # Check that the prompt contains the expected parts
56
+ assert summary_from_first_prompt in prompt_fr
57
+ assert (
58
+ SIGNAL_INTRO["fr"].format(summary_from_first_prompt=summary_from_first_prompt)
59
+ in prompt_fr
60
+ )
61
+ assert SIGNAL_INSTRUCTIONS["fr"] in prompt_fr
62
+
63
+
64
+ def test_get_prompt_topic_summary():
65
+ """Test that get_prompt returns the correct topic summary prompt."""
66
+ topic_number = 42
67
+ content_summary = "This is a test content summary"
68
+
69
+ # Test English prompt
70
+ prompt_en = get_prompt(
71
+ language="English",
72
+ prompt_type="topic_summary",
73
+ topic_number=topic_number,
74
+ content_summary=content_summary,
75
+ )
76
+
77
+ # Check that the prompt contains the expected parts
78
+ assert str(topic_number) in prompt_en
79
+ assert content_summary in prompt_en
80
+ assert (
81
+ TOPIC_SUMMARY_PROMPT["en"].format(
82
+ topic_number=topic_number, content_summary=content_summary
83
+ )
84
+ == prompt_en
85
+ )
86
+
87
+ # Test French prompt
88
+ prompt_fr = get_prompt(
89
+ language="French",
90
+ prompt_type="topic_summary",
91
+ topic_number=topic_number,
92
+ content_summary=content_summary,
93
+ )
94
+
95
+ # Check that the prompt contains the expected parts
96
+ assert str(topic_number) in prompt_fr
97
+ assert content_summary in prompt_fr
98
+ assert (
99
+ TOPIC_SUMMARY_PROMPT["fr"].format(
100
+ topic_number=topic_number, content_summary=content_summary
101
+ )
102
+ == prompt_fr
103
+ )
104
+
105
+
106
+ def test_get_prompt_invalid_type():
107
+ """Test that get_prompt raises an error for invalid prompt types."""
108
+ with pytest.raises(ValueError):
109
+ get_prompt(
110
+ language="English",
111
+ prompt_type="invalid_type",
112
+ )
113
+
114
+
115
+ @patch("bertrend.trend_analysis.prompts.OUTPUT_PATH")
116
+ @patch("builtins.open", new_callable=mock_open)
117
+ def test_save_html_output(mock_file, mock_output_path):
118
+ """Test that save_html_output correctly saves HTML output."""
119
+ # Setup
120
+ mock_output_path.__truediv__.return_value = "mocked_path/signal_llm.html"
121
+ html_output = "<html><body>Test HTML</body></html>"
122
+
123
+ # Call the function
124
+ save_html_output(html_output)
125
+
126
+ # Check that the file was opened with the correct path
127
+ mock_output_path.__truediv__.assert_called_once_with("signal_llm.html")
128
+
129
+ # Check that the file was written with the correct content
130
+ mock_file.assert_called_once_with(
131
+ "mocked_path/signal_llm.html", "w", encoding="utf-8"
132
+ )
133
+ mock_file().write.assert_called_once_with(html_output)
134
+
135
+
136
+ @patch("bertrend.trend_analysis.prompts.Environment")
137
+ def test_fill_html_template(mock_environment):
138
+ """Test that fill_html_template correctly fills the HTML template."""
139
+ # Setup mock template
140
+ mock_template = mock_environment.return_value.get_template.return_value
141
+ mock_template.render.return_value = "<html>\n<body>\nTest HTML\n</body>\n</html>\n"
142
+
143
+ # Create test data
144
+ topic_summary = TopicSummary(
145
+ title="Test Topic",
146
+ date="2023-01-01",
147
+ key_developments=["Development 1"],
148
+ description="Description",
149
+ novelty="Novelty",
150
+ )
151
+
152
+ topic_summary_list = TopicSummaryList(topic_summary_by_time_period=[topic_summary])
153
+
154
+ implications = PotentialImplications(
155
+ long_term_implications=["Long term"],
156
+ short_term_implications=["Short term"],
157
+ )
158
+
159
+ scenario = EvolutionScenario(
160
+ optimistic_scenario_description="Optimistic",
161
+ optimistic_scenario_points=["Optimistic point"],
162
+ pessimistic_scenario_description="Pessimistic",
163
+ pessimistic_scenario_points=["Pessimistic point"],
164
+ )
165
+
166
+ interconnexions = TopicInterconnexions(
167
+ interconnexions=["Interconnexion"],
168
+ ripple_effects=["Ripple effect"],
169
+ )
170
+
171
+ drivers = Drivers(
172
+ drivers=["Driver"],
173
+ inhibitors=["Inhibitor"],
174
+ )
175
+
176
+ signal_analysis = SignalAnalysis(
177
+ potential_implications=implications,
178
+ evolution_scenario=scenario,
179
+ topic_interconnexions=interconnexions,
180
+ drivers_inhibitors=drivers,
181
+ )
182
+
183
+ # Call the function with English language
184
+ result_en = fill_html_template(
185
+ topic_summary_list=topic_summary_list,
186
+ signal_analysis=signal_analysis,
187
+ language="en",
188
+ )
189
+
190
+ # Check that the template was rendered with the correct data
191
+ mock_environment.return_value.get_template.assert_called_with(
192
+ "signal_llm_template_en.html"
193
+ )
194
+ mock_template.render.assert_called_with(
195
+ topic_summary_list=topic_summary_list,
196
+ signal_analysis=signal_analysis,
197
+ )
198
+
199
+ # Check that newlines were removed
200
+ assert "\n" not in result_en
201
+ assert result_en == "<html><body>Test HTML</body></html>"
202
+
203
+ # Reset mocks
204
+ mock_environment.reset_mock()
205
+ mock_template.render.reset_mock()
206
+
207
+ # Call the function with French language
208
+ result_fr = fill_html_template(
209
+ topic_summary_list=topic_summary_list,
210
+ signal_analysis=signal_analysis,
211
+ language="fr",
212
+ )
213
+
214
+ # Check that the template was rendered with the correct data
215
+ mock_environment.return_value.get_template.assert_called_with(
216
+ "signal_llm_template_fr.html"
217
+ )
218
+ mock_template.render.assert_called_with(
219
+ topic_summary_list=topic_summary_list,
220
+ signal_analysis=signal_analysis,
221
+ )
222
+
223
+ # Check that newlines were removed
224
+ assert "\n" not in result_fr
225
+ assert result_fr == "<html><body>Test HTML</body></html>"
@@ -0,0 +1,307 @@
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 pytest
7
+ import pandas as pd
8
+ from unittest.mock import patch, MagicMock
9
+ from plotly import graph_objects as go
10
+
11
+ from bertrend.trend_analysis.visualizations import (
12
+ plot_num_topics,
13
+ plot_size_outliers,
14
+ plot_topics_for_model,
15
+ create_topic_size_evolution_figure,
16
+ plot_topic_size_evolution,
17
+ plot_newly_emerged_topics,
18
+ create_sankey_diagram_plotly,
19
+ )
20
+
21
+
22
+ @pytest.fixture
23
+ def mock_topic_models():
24
+ """Create mock topic models for testing."""
25
+ model1 = MagicMock()
26
+ model1.get_topic_info.return_value = pd.DataFrame(
27
+ {
28
+ "Topic": [0, 1, 2, -1],
29
+ "Count": [10, 15, 5, 3],
30
+ "Name": ["Topic 0", "Topic 1", "Topic 2", "Outlier"],
31
+ "Representation": [
32
+ ["word1", "word2"],
33
+ ["word3", "word4"],
34
+ ["word5", "word6"],
35
+ ["outlier"],
36
+ ],
37
+ }
38
+ )
39
+
40
+ model2 = MagicMock()
41
+ model2.get_topic_info.return_value = pd.DataFrame(
42
+ {
43
+ "Topic": [0, 1, -1],
44
+ "Count": [12, 18, 5],
45
+ "Name": ["Topic 0", "Topic 1", "Outlier"],
46
+ "Representation": [["word1", "word2"], ["word3", "word4"], ["outlier"]],
47
+ }
48
+ )
49
+
50
+ return {pd.Timestamp("2023-01-01"): model1, pd.Timestamp("2023-02-01"): model2}
51
+
52
+
53
+ @pytest.fixture
54
+ def mock_selected_model():
55
+ """Create a mock selected model for testing."""
56
+ model = MagicMock()
57
+ model.doc_info_df = pd.DataFrame(
58
+ {
59
+ "source": ["source1", "source1", "source2", "source2", "source3"],
60
+ "Topic": [0, 1, 0, 1, 2],
61
+ "document_id": ["doc1", "doc2", "doc3", "doc4", "doc5"],
62
+ "Representation": [
63
+ ["word1", "word2"],
64
+ ["word3", "word4"],
65
+ ["word1", "word2"],
66
+ ["word3", "word4"],
67
+ ["word5", "word6"],
68
+ ],
69
+ }
70
+ )
71
+ return model
72
+
73
+
74
+ @pytest.fixture
75
+ def mock_topic_sizes():
76
+ """Create mock topic sizes for testing."""
77
+ return {
78
+ 0: {
79
+ "Timestamps": [pd.Timestamp("2023-01-01"), pd.Timestamp("2023-02-01")],
80
+ "Popularity": [10, 15],
81
+ "Representations": ["word1_word2", "word1_word2_word3"],
82
+ },
83
+ 1: {
84
+ "Timestamps": [pd.Timestamp("2023-01-01"), pd.Timestamp("2023-02-01")],
85
+ "Popularity": [8, 12],
86
+ "Representations": ["word3_word4", "word3_word4_word5"],
87
+ },
88
+ }
89
+
90
+
91
+ @pytest.fixture
92
+ def mock_all_new_topics_df():
93
+ """Create mock all_new_topics_df for testing."""
94
+ return pd.DataFrame(
95
+ {
96
+ "Timestamp": [
97
+ pd.Timestamp("2023-01-01"),
98
+ pd.Timestamp("2023-01-01"),
99
+ pd.Timestamp("2023-02-01"),
100
+ ],
101
+ "Topic": [0, 1, 2],
102
+ "Count": [10, 15, 20],
103
+ "Document_Count": [5, 8, 10],
104
+ "Representation": [
105
+ ["word1", "word2"],
106
+ ["word3", "word4"],
107
+ ["word5", "word6"],
108
+ ],
109
+ }
110
+ )
111
+
112
+
113
+ @pytest.fixture
114
+ def mock_all_merge_histories_df():
115
+ """Create mock all_merge_histories_df for testing."""
116
+ return pd.DataFrame(
117
+ {
118
+ "Timestamp": [pd.Timestamp("2023-01-01"), pd.Timestamp("2023-02-01")],
119
+ "Topic1": [0, 1],
120
+ "Topic2": [1, 2],
121
+ "Count1": [10, 15],
122
+ "Count2": [15, 20],
123
+ "Document_Count1": [5, 8],
124
+ "Document_Count2": [8, 10],
125
+ "Representation1": [["word1", "word2"], ["word3", "word4"]],
126
+ "Representation2": [["word3", "word4"], ["word5", "word6"]],
127
+ "Documents1": [["doc1", "doc2"], ["doc3", "doc4"]],
128
+ "Documents2": [["doc3", "doc4"], ["doc5", "doc6"]],
129
+ "Source1": [["source1"], ["source2"]],
130
+ "Source2": [["source2"], ["source3"]],
131
+ }
132
+ )
133
+
134
+
135
+ def test_plot_num_topics(mock_topic_models):
136
+ """Test that plot_num_topics returns a figure with the correct data."""
137
+ fig = plot_num_topics(mock_topic_models)
138
+
139
+ # Check that the result is a Figure
140
+ assert isinstance(fig, go.Figure)
141
+
142
+ # Check that the figure has the correct data
143
+ assert len(fig.data) == 1
144
+ assert fig.data[0].x[0] == pd.Timestamp("2023-01-01")
145
+ assert fig.data[0].x[1] == pd.Timestamp("2023-02-01")
146
+ assert fig.data[0].y[0] == 4 # Number of topics in model1
147
+ assert fig.data[0].y[1] == 3 # Number of topics in model2
148
+
149
+
150
+ def test_plot_size_outliers(mock_topic_models):
151
+ """Test that plot_size_outliers returns a figure with the correct data."""
152
+ fig = plot_size_outliers(mock_topic_models)
153
+
154
+ # Check that the result is a Figure
155
+ assert isinstance(fig, go.Figure)
156
+
157
+ # Check that the figure has the correct data
158
+ assert len(fig.data) == 1
159
+ assert fig.data[0].x[0] == pd.Timestamp("2023-01-01")
160
+ assert fig.data[0].x[1] == pd.Timestamp("2023-02-01")
161
+ assert fig.data[0].y[0] == 3 # Size of outlier topic in model1
162
+ assert fig.data[0].y[1] == 5 # Size of outlier topic in model2
163
+
164
+
165
+ def test_plot_topics_for_model(mock_selected_model):
166
+ """Test that plot_topics_for_model returns a figure with the correct data."""
167
+ fig = plot_topics_for_model(mock_selected_model)
168
+
169
+ # Check that the result is a Figure
170
+ assert isinstance(fig, go.Figure)
171
+
172
+ # Check that the figure has the correct number of traces (one per topic)
173
+ assert len(fig.data) == 3
174
+
175
+
176
+ def test_create_topic_size_evolution_figure(mock_topic_sizes):
177
+ """Test that create_topic_size_evolution_figure returns a figure with the correct data."""
178
+ # Test with all topics
179
+ fig_all = create_topic_size_evolution_figure(mock_topic_sizes)
180
+
181
+ # Check that the result is a Figure
182
+ assert isinstance(fig_all, go.Figure)
183
+
184
+ # Check that the figure has the correct number of traces (one per topic)
185
+ assert len(fig_all.data) == 2
186
+
187
+ # Test with specific topics
188
+ fig_specific = create_topic_size_evolution_figure(mock_topic_sizes, topic_ids=[0])
189
+
190
+ # Check that the result is a Figure
191
+ assert isinstance(fig_specific, go.Figure)
192
+
193
+ # Check that the figure has the correct number of traces (one for the specified topic)
194
+ assert len(fig_specific.data) == 1
195
+ assert fig_specific.data[0].name.startswith("Topic 0")
196
+
197
+
198
+ def test_plot_topic_size_evolution():
199
+ """Test that plot_topic_size_evolution returns a figure with the correct data."""
200
+ # Create a base figure
201
+ fig = go.Figure()
202
+
203
+ # Call the function
204
+ result = plot_topic_size_evolution(
205
+ fig=fig,
206
+ current_date=pd.Timestamp("2023-02-01"),
207
+ window_start=pd.Timestamp("2023-01-01"),
208
+ window_end=pd.Timestamp("2023-03-01"),
209
+ all_popularity_values=[5, 10, 15, 20],
210
+ q1=7,
211
+ q3=17,
212
+ )
213
+
214
+ # Check that the result is a Figure
215
+ assert isinstance(result, go.Figure)
216
+
217
+ # Check that the figure has the correct layout
218
+ assert result.layout.title.text == "Popularity Evolution"
219
+ assert result.layout.xaxis.title.text == "Timestamp"
220
+ assert result.layout.yaxis.title.text == "Popularity"
221
+
222
+ # Check that the figure has the correct shapes (vertical line for current date and 3 horizontal regions)
223
+ assert len(result.layout.shapes) == 4
224
+
225
+ # First shape should be the vertical line for current date
226
+ assert result.layout.shapes[0].type == "line"
227
+ assert result.layout.shapes[0].x0 == pd.Timestamp("2023-02-01")
228
+ assert result.layout.shapes[0].x1 == pd.Timestamp("2023-02-01")
229
+
230
+ # Next three shapes should be the horizontal rectangles for noise, weak signal, and strong signal regions
231
+ assert result.layout.shapes[1].type == "rect" # Noise region (grey)
232
+ assert result.layout.shapes[1].fillcolor == "rgba(128, 128, 128, 0.2)"
233
+ assert result.layout.shapes[1].y1 == 7 # q1
234
+
235
+ assert result.layout.shapes[2].type == "rect" # Weak signal region (orange)
236
+ assert result.layout.shapes[2].fillcolor == "rgba(255, 165, 0, 0.2)"
237
+ assert result.layout.shapes[2].y0 == 7 # q1
238
+ assert result.layout.shapes[2].y1 == 17 # q3
239
+
240
+ assert result.layout.shapes[3].type == "rect" # Strong signal region (green)
241
+ assert result.layout.shapes[3].fillcolor == "rgba(0, 255, 0, 0.2)"
242
+ assert result.layout.shapes[3].y0 == 17 # q3
243
+
244
+ # Check that the figure has the correct annotations (current date)
245
+ assert len(result.layout.annotations) == 1
246
+ assert result.layout.annotations[0].x == pd.Timestamp("2023-02-01")
247
+
248
+
249
+ def test_plot_newly_emerged_topics(mock_all_new_topics_df):
250
+ """Test that plot_newly_emerged_topics returns a figure with the correct data."""
251
+ fig = plot_newly_emerged_topics(mock_all_new_topics_df)
252
+
253
+ # Check that the result is a Figure
254
+ assert isinstance(fig, go.Figure)
255
+
256
+ # Check that the figure has the correct number of traces (one per timestamp group)
257
+ assert (
258
+ len(fig.data) == 2
259
+ ) # One for 2023-01-01 (with 2 topics) and one for 2023-02-01 (with 1 topic)
260
+
261
+
262
+ @patch("bertrend.trend_analysis.visualizations._transform_merge_histories_for_sankey")
263
+ def test_create_sankey_diagram_plotly(mock_transform, mock_all_merge_histories_df):
264
+ """Test that create_sankey_diagram_plotly returns a figure with the correct data."""
265
+ # Setup mock transformed dataframe
266
+ mock_transform.return_value = pd.DataFrame(
267
+ {
268
+ "Source": ["0_1_0", "0_2_1", "1_1_1", "1_2_2"],
269
+ "Destination": ["1_1_0", "1_1_0", "2_1_1", "2_1_1"],
270
+ "Representation": [
271
+ ("word1", "word2"),
272
+ ("word3", "word4"),
273
+ ("word3", "word4"),
274
+ ("word5", "word6"),
275
+ ],
276
+ "Timestamp": [
277
+ pd.Timestamp("2023-01-01"),
278
+ pd.Timestamp("2023-01-01"),
279
+ pd.Timestamp("2023-02-01"),
280
+ pd.Timestamp("2023-02-01"),
281
+ ],
282
+ "Count": [5, 8, 8, 10],
283
+ }
284
+ )
285
+
286
+ # Call the function
287
+ fig = create_sankey_diagram_plotly(mock_all_merge_histories_df)
288
+
289
+ # Check that the result is a Figure
290
+ assert isinstance(fig, go.Figure)
291
+
292
+ # Check that the figure has the correct data
293
+ assert len(fig.data) == 1
294
+ assert fig.data[0].type == "sankey"
295
+
296
+ # Check that the transform function was called with the correct arguments
297
+ mock_transform.assert_called_once_with(mock_all_merge_histories_df)
298
+
299
+ # Test with search_term
300
+ fig_search = create_sankey_diagram_plotly(
301
+ mock_all_merge_histories_df, search_term="word1"
302
+ )
303
+ assert isinstance(fig_search, go.Figure)
304
+
305
+ # Test with max_pairs
306
+ fig_max = create_sankey_diagram_plotly(mock_all_merge_histories_df, max_pairs=2)
307
+ assert isinstance(fig_max, go.Figure)