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,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)
|