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,317 @@
|
|
|
1
|
+
import tomllib
|
|
2
|
+
import json
|
|
3
|
+
from bertopic import BERTopic
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import streamlit as st
|
|
9
|
+
from streamlit.runtime.uploaded_file_manager import UploadedFile
|
|
10
|
+
import plotly.graph_objects as go
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
from bertrend import LLM_CONFIG
|
|
14
|
+
from bertrend.BERTopicModel import BERTopicModel
|
|
15
|
+
from bertrend.llm_utils.openai_client import OpenAI_Client
|
|
16
|
+
from bertrend.services.embedding_service import EmbeddingService
|
|
17
|
+
from bertrend_apps.exploration.curebot.prompts import (
|
|
18
|
+
TOPIC_DESCRIPTION_SYSTEM_PROMPT,
|
|
19
|
+
TOPIC_SUMMARY_SYSTEM_PROMPT,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Get configuration file
|
|
23
|
+
CONFIG = tomllib.load(open(Path(__file__).parent / "config.toml", "rb"))
|
|
24
|
+
|
|
25
|
+
# Set curebot column name
|
|
26
|
+
URL_COLUMN = CONFIG["data"]["url_column"]
|
|
27
|
+
TEXT_COLUMN = CONFIG["data"]["text_column"]
|
|
28
|
+
TITLE_COLUMN = CONFIG["data"]["title_column"]
|
|
29
|
+
SOURCE_COLUMN = CONFIG["data"]["source_column"]
|
|
30
|
+
TIMESTAMP_COLUMN = CONFIG["data"]["timestamp_column"]
|
|
31
|
+
TAGS_COLUMN = CONFIG["data"]["tags_column"]
|
|
32
|
+
|
|
33
|
+
# Topics config
|
|
34
|
+
TOP_N_WORDS = CONFIG["topics"]["top_n_words"]
|
|
35
|
+
|
|
36
|
+
# Newsletter
|
|
37
|
+
NEWSLETTER_TEMPLATE = CONFIG["newsletter"]["template"]
|
|
38
|
+
|
|
39
|
+
# Load embdding model
|
|
40
|
+
EMBEDDING_SERVICE = EmbeddingService(local=False)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@st.cache_data(show_spinner=False)
|
|
44
|
+
def concat_data_from_files(files: list[UploadedFile]) -> pd.DataFrame:
|
|
45
|
+
"""
|
|
46
|
+
Concatenate data from multiple Excel files into a single DataFrame.
|
|
47
|
+
"""
|
|
48
|
+
df_list = []
|
|
49
|
+
for file in files:
|
|
50
|
+
df_list.append(pd.read_excel(file))
|
|
51
|
+
df = pd.concat(df_list, ignore_index=True)
|
|
52
|
+
|
|
53
|
+
return df
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@st.cache_data(show_spinner=False)
|
|
57
|
+
def chunk_df(
|
|
58
|
+
df: pd.DataFrame, chunk_size: int = 100, overlap: int = 20
|
|
59
|
+
) -> pd.DataFrame:
|
|
60
|
+
"""
|
|
61
|
+
Split df texts into overlapping chunks while preserving other columns.
|
|
62
|
+
|
|
63
|
+
Parameters:
|
|
64
|
+
-----------
|
|
65
|
+
df : pandas.DataFrame
|
|
66
|
+
Input DataFrame containing text and other columns
|
|
67
|
+
chunk_size : int
|
|
68
|
+
Number of words in each chunk
|
|
69
|
+
overlap : int
|
|
70
|
+
Number of words to overlap between chunks
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
--------
|
|
74
|
+
pandas.DataFrame
|
|
75
|
+
DataFrame with chunked texts, preserving other column values
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# # Apply chunking to each row
|
|
79
|
+
# chunked_data = []
|
|
80
|
+
# for _, row in df.iterrows():
|
|
81
|
+
# chunked_data.extend(split_text_to_chunks(row, chunk_size, overlap))
|
|
82
|
+
|
|
83
|
+
# # Create new DataFrame from chunked data
|
|
84
|
+
# df_split = (
|
|
85
|
+
# pd.DataFrame(chunked_data)
|
|
86
|
+
# .sort_values(by=TIMESTAMP_COLUMN)
|
|
87
|
+
# .reset_index(drop=True)
|
|
88
|
+
# )
|
|
89
|
+
|
|
90
|
+
return df.copy()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@st.cache_data(show_spinner=False)
|
|
94
|
+
def get_embeddings(texts: list[str]) -> np.ndarray:
|
|
95
|
+
"""Get embeddings for a list of texts."""
|
|
96
|
+
embeddings, _, _ = EMBEDDING_SERVICE.embed(texts)
|
|
97
|
+
return embeddings
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@st.cache_data(show_spinner=False)
|
|
101
|
+
def fit_bertopic(
|
|
102
|
+
docs: list[str],
|
|
103
|
+
embeddings: np.ndarray,
|
|
104
|
+
min_articles_per_topic: int,
|
|
105
|
+
zeroshot_topic_list: list[str] | None = None,
|
|
106
|
+
) -> tuple[BERTopic, list[int]]:
|
|
107
|
+
"""
|
|
108
|
+
Fit BERTopic model on a list of documents and their embeddings.
|
|
109
|
+
"""
|
|
110
|
+
# Override default parameters
|
|
111
|
+
bertopic_config = {"hdbscan_model": {"min_cluster_size": min_articles_per_topic}}
|
|
112
|
+
# Initialize topic model
|
|
113
|
+
topic_model = BERTopicModel(config=bertopic_config)
|
|
114
|
+
|
|
115
|
+
# Train topic model
|
|
116
|
+
topic_model_output = topic_model.fit(
|
|
117
|
+
docs=docs,
|
|
118
|
+
embeddings=embeddings,
|
|
119
|
+
zeroshot_topic_list=zeroshot_topic_list,
|
|
120
|
+
zeroshot_min_similarity=0.65,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return topic_model_output.topic_model, topic_model_output.topics
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@st.cache_data(show_spinner=False)
|
|
127
|
+
def get_improved_topic_description(
|
|
128
|
+
df: pd.DataFrame, _topics_info: pd.DataFrame
|
|
129
|
+
) -> list[str]:
|
|
130
|
+
"""Get improved topic description using LLM."""
|
|
131
|
+
# Get llm client
|
|
132
|
+
llm_client = OpenAI_Client(
|
|
133
|
+
api_key=LLM_CONFIG["api_key"],
|
|
134
|
+
endpoint=LLM_CONFIG["endpoint"],
|
|
135
|
+
model=LLM_CONFIG["model"],
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# List of improved topics description
|
|
139
|
+
improved_descriptions = []
|
|
140
|
+
|
|
141
|
+
# Loop over topics
|
|
142
|
+
for topic_number in range(len(_topics_info)):
|
|
143
|
+
topic_df = df[df["topics"] == topic_number]
|
|
144
|
+
user_prompt = "\n\n".join(
|
|
145
|
+
topic_df.apply(
|
|
146
|
+
lambda row: f"Titre : {row[TITLE_COLUMN]}\nArticle : {row[TEXT_COLUMN][0:2000]}...",
|
|
147
|
+
axis=1,
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
response = llm_client.generate(
|
|
151
|
+
user_prompt=user_prompt,
|
|
152
|
+
system_prompt=TOPIC_DESCRIPTION_SYSTEM_PROMPT,
|
|
153
|
+
response_format={"type": "json_object"},
|
|
154
|
+
)
|
|
155
|
+
improved_descriptions.append(json.loads(response)["titre"])
|
|
156
|
+
|
|
157
|
+
return improved_descriptions
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@st.cache_data(show_spinner=False)
|
|
161
|
+
def create_newsletter(
|
|
162
|
+
df: pd.DataFrame,
|
|
163
|
+
topics_info: pd.DataFrame,
|
|
164
|
+
nb_topics: int,
|
|
165
|
+
nb_articles_per_topic: int,
|
|
166
|
+
):
|
|
167
|
+
"""
|
|
168
|
+
Create newsletter dict from containing the newsletter content in the following format:
|
|
169
|
+
{
|
|
170
|
+
"title": "Newsletter",
|
|
171
|
+
"min_timestamp": "Monday 01 January 2025",
|
|
172
|
+
"max_timestamp": "Sunday 07 January 2025",
|
|
173
|
+
"topics": [
|
|
174
|
+
{
|
|
175
|
+
"title": "Topic 1",
|
|
176
|
+
"keywords": "#keyword1 #keyword2 #keyword3",
|
|
177
|
+
"summary": "Summary of topic 1"
|
|
178
|
+
"articles": [
|
|
179
|
+
{
|
|
180
|
+
"title": "Article 1",
|
|
181
|
+
"url": "https://www.article1.com",
|
|
182
|
+
"timestamp": "Monday 01 January 2023",
|
|
183
|
+
"source": "Source 1"
|
|
184
|
+
},
|
|
185
|
+
...
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
...
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
"""
|
|
192
|
+
# Create newsletter dict that stores the newsletter content
|
|
193
|
+
newsletter_dict = {"title": "Newsletter"}
|
|
194
|
+
|
|
195
|
+
# Get min and max date of the articles in the dataframe
|
|
196
|
+
newsletter_dict["min_timestamp"] = (
|
|
197
|
+
df[TIMESTAMP_COLUMN].min().strftime("%A %d %B %Y")
|
|
198
|
+
)
|
|
199
|
+
newsletter_dict["max_timestamp"] = (
|
|
200
|
+
df[TIMESTAMP_COLUMN].max().strftime("%A %d %B %Y")
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
newsletter_dict["topics"] = []
|
|
204
|
+
for i in range(nb_topics):
|
|
205
|
+
# Dict to store topic info
|
|
206
|
+
topic_dict = {}
|
|
207
|
+
|
|
208
|
+
# Get title and key words
|
|
209
|
+
topic_dict["title"] = topics_info.iloc[i]["llm_description"]
|
|
210
|
+
topic_dict["keywords"] = (
|
|
211
|
+
"#" + " #".join(topics_info.iloc[i]["Representation"]).strip()
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Filter df to get articles for the topic
|
|
215
|
+
topic_df = df[df["topics"] == i]
|
|
216
|
+
|
|
217
|
+
# Get first `newsletter_nb_articles_per_topic` articles for the topic
|
|
218
|
+
topic_df = topic_df.head(min(nb_articles_per_topic, len(topic_df)))
|
|
219
|
+
|
|
220
|
+
# Get a summary of the topic
|
|
221
|
+
user_prompt = "\n\n".join(
|
|
222
|
+
topic_df.apply(
|
|
223
|
+
lambda row: f"Titre : {row[TITLE_COLUMN]}\nArticle : {row[TEXT_COLUMN][0:2000]}...",
|
|
224
|
+
axis=1,
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
llm_client = OpenAI_Client(
|
|
228
|
+
api_key=LLM_CONFIG["api_key"],
|
|
229
|
+
endpoint=LLM_CONFIG["endpoint"],
|
|
230
|
+
model=LLM_CONFIG["model"],
|
|
231
|
+
)
|
|
232
|
+
response = llm_client.generate(
|
|
233
|
+
user_prompt=user_prompt,
|
|
234
|
+
system_prompt=TOPIC_SUMMARY_SYSTEM_PROMPT,
|
|
235
|
+
response_format={"type": "json_object"},
|
|
236
|
+
)
|
|
237
|
+
topic_dict["summary"] = json.loads(response)["résumé"]
|
|
238
|
+
topic_dict["articles"] = []
|
|
239
|
+
for _, row in topic_df.iterrows():
|
|
240
|
+
article_dict = {}
|
|
241
|
+
article_dict["title"] = row[TITLE_COLUMN]
|
|
242
|
+
article_dict["url"] = row[URL_COLUMN]
|
|
243
|
+
article_dict["timestamp"] = row[TIMESTAMP_COLUMN].strftime("%A %d %B %Y")
|
|
244
|
+
article_dict["source"] = row[SOURCE_COLUMN]
|
|
245
|
+
topic_dict["articles"].append(article_dict)
|
|
246
|
+
newsletter_dict["topics"].append(topic_dict)
|
|
247
|
+
return newsletter_dict
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def display_source_distribution(
|
|
251
|
+
representative_df: pd.DataFrame, selected_sources: list[str]
|
|
252
|
+
):
|
|
253
|
+
"""Display the distribution of sources in a pie chart."""
|
|
254
|
+
|
|
255
|
+
source_counts = representative_df[URL_COLUMN].apply(get_website_name).value_counts()
|
|
256
|
+
|
|
257
|
+
# Create a list to store the 'pull' values for each slice
|
|
258
|
+
pull = []
|
|
259
|
+
|
|
260
|
+
# Determine which slices should be pulled out
|
|
261
|
+
for source in source_counts.index:
|
|
262
|
+
if source in selected_sources and "All" not in selected_sources:
|
|
263
|
+
pull.append(0.2)
|
|
264
|
+
else:
|
|
265
|
+
pull.append(0)
|
|
266
|
+
|
|
267
|
+
fig = go.Figure(
|
|
268
|
+
data=[
|
|
269
|
+
go.Pie(
|
|
270
|
+
labels=source_counts.index,
|
|
271
|
+
values=source_counts.values,
|
|
272
|
+
pull=pull,
|
|
273
|
+
textposition="inside",
|
|
274
|
+
textinfo="percent+label",
|
|
275
|
+
hole=0.3,
|
|
276
|
+
)
|
|
277
|
+
]
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
fig.update_layout(
|
|
281
|
+
showlegend=False, height=600, width=500, margin=dict(t=0, b=0, l=0, r=0)
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
st.plotly_chart(fig, use_container_width=True)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def get_website_name(url):
|
|
288
|
+
"""Extract website name from URL, handling None, NaN, and invalid URLs."""
|
|
289
|
+
if pd.isna(url) or url is None or isinstance(url, float):
|
|
290
|
+
return "Unknown Source"
|
|
291
|
+
try:
|
|
292
|
+
return (
|
|
293
|
+
urlparse(str(url)).netloc.replace("www.", "").split(".")[0]
|
|
294
|
+
or "Unknown Source"
|
|
295
|
+
)
|
|
296
|
+
except:
|
|
297
|
+
return "Unknown Source"
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def display_representative_documents(filtered_df: pd.DataFrame):
|
|
301
|
+
"""Display representative documents for the selected topic."""
|
|
302
|
+
with st.container(border=False, height=600):
|
|
303
|
+
for _, doc in filtered_df.iterrows():
|
|
304
|
+
website_name = get_website_name(doc[URL_COLUMN])
|
|
305
|
+
date = doc[TIMESTAMP_COLUMN].strftime("%A %d %b %Y %H:%M:%S")
|
|
306
|
+
snippet = (
|
|
307
|
+
doc[TEXT_COLUMN][:200] + "..."
|
|
308
|
+
if len(doc[TEXT_COLUMN]) > 200
|
|
309
|
+
else doc[TEXT_COLUMN]
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
content = f"""**{doc[TITLE_COLUMN]}**\n\n{date} | {'Unknown Source' if website_name == 'Unknown Source' else website_name}\n\n{snippet}"""
|
|
313
|
+
|
|
314
|
+
if website_name != "Unknown Source":
|
|
315
|
+
st.link_button(content, doc[URL_COLUMN])
|
|
316
|
+
else:
|
|
317
|
+
st.markdown(content)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[embedding]
|
|
2
|
+
model_name = "dangvantuan/french-document-embedding"
|
|
3
|
+
|
|
4
|
+
[data]
|
|
5
|
+
title_column = "Titre de la ressource"
|
|
6
|
+
text_column = "Contenu de la ressource"
|
|
7
|
+
url_column = "URL de la ressource"
|
|
8
|
+
source_column = "Domaine de la ressource"
|
|
9
|
+
timestamp_column = "Date de trouvaille"
|
|
10
|
+
tags_column = "Tags"
|
|
11
|
+
|
|
12
|
+
[topics]
|
|
13
|
+
top_n_words = 5
|
|
14
|
+
|
|
15
|
+
[newsletter]
|
|
16
|
+
template = """
|
|
17
|
+
# {{ title }}
|
|
18
|
+
|
|
19
|
+
*Du {{ min_timestamp }} au {{ max_timestamp }}.*
|
|
20
|
+
|
|
21
|
+
{% for topic in topics %}
|
|
22
|
+
## {{ topic.title }}
|
|
23
|
+
|
|
24
|
+
**{{ topic.keywords }}**
|
|
25
|
+
|
|
26
|
+
{{ topic.summary }}
|
|
27
|
+
|
|
28
|
+
{% for article in topic.articles %}
|
|
29
|
+
* [{{ article.title }}]({{ article.url }}) - **{{ article.source }}** - *{{ article.timestamp }}*
|
|
30
|
+
|
|
31
|
+
{% endfor %}
|
|
32
|
+
{% endfor %}
|
|
33
|
+
"""
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"metadata": {},
|
|
5
|
+
"cell_type": "markdown",
|
|
6
|
+
"source": "Import articles extracted from Curebot tool",
|
|
7
|
+
"id": "3c7e4968da937628"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"metadata": {},
|
|
11
|
+
"cell_type": "code",
|
|
12
|
+
"source": [
|
|
13
|
+
"from pathlib import Path\n",
|
|
14
|
+
"import pandas as pd\n",
|
|
15
|
+
"from bertrend_apps.data_provider.curebot_provider import CurebotProvider\n",
|
|
16
|
+
"from loguru import logger\n",
|
|
17
|
+
"from typing import Dict, List, Optional\n",
|
|
18
|
+
"import dateparser\n"
|
|
19
|
+
],
|
|
20
|
+
"id": "4a4893bf8790823b",
|
|
21
|
+
"outputs": [],
|
|
22
|
+
"execution_count": null
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"metadata": {},
|
|
26
|
+
"cell_type": "code",
|
|
27
|
+
"source": "file_path = Path(\"/home/jerome/shared/synthèses veille énergie/Extraction article VA Energie mars-avril 24 pour IA.xlsx\")\n",
|
|
28
|
+
"id": "4f83820d787a541a",
|
|
29
|
+
"outputs": [],
|
|
30
|
+
"execution_count": null
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"metadata": {},
|
|
34
|
+
"cell_type": "code",
|
|
35
|
+
"source": [
|
|
36
|
+
"# Extract full text from resources\n",
|
|
37
|
+
"provider = CurebotProvider(file_path)"
|
|
38
|
+
],
|
|
39
|
+
"id": "4d3eda06ea551b8f",
|
|
40
|
+
"outputs": [],
|
|
41
|
+
"execution_count": null
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"metadata": {},
|
|
45
|
+
"cell_type": "code",
|
|
46
|
+
"source": "articles = provider.get_articles()",
|
|
47
|
+
"id": "2609aa269898c1d5",
|
|
48
|
+
"outputs": [],
|
|
49
|
+
"execution_count": null
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"metadata": {},
|
|
53
|
+
"cell_type": "code",
|
|
54
|
+
"source": [
|
|
55
|
+
"len(articles)\n",
|
|
56
|
+
"\n"
|
|
57
|
+
],
|
|
58
|
+
"id": "35d63ab115d45ca0",
|
|
59
|
+
"outputs": [],
|
|
60
|
+
"execution_count": null
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"metadata": {},
|
|
64
|
+
"cell_type": "code",
|
|
65
|
+
"source": "print(articles[0]['text'])",
|
|
66
|
+
"id": "9368a9ec655007cf",
|
|
67
|
+
"outputs": [],
|
|
68
|
+
"execution_count": null
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"metadata": {},
|
|
72
|
+
"cell_type": "code",
|
|
73
|
+
"source": "output_file = '/home/jerome/shared/curebot_articles.jsonl'",
|
|
74
|
+
"id": "cd1e15ca55c99788",
|
|
75
|
+
"outputs": [],
|
|
76
|
+
"execution_count": null
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"metadata": {},
|
|
80
|
+
"cell_type": "code",
|
|
81
|
+
"source": "provider.store_articles(articles, output_file)",
|
|
82
|
+
"id": "ca0e88d25f0d84c3",
|
|
83
|
+
"outputs": [],
|
|
84
|
+
"execution_count": null
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"metadata": {
|
|
88
|
+
"kernelspec": {
|
|
89
|
+
"display_name": "Python 3",
|
|
90
|
+
"language": "python",
|
|
91
|
+
"name": "python3"
|
|
92
|
+
},
|
|
93
|
+
"language_info": {
|
|
94
|
+
"codemirror_mode": {
|
|
95
|
+
"name": "ipython",
|
|
96
|
+
"version": 2
|
|
97
|
+
},
|
|
98
|
+
"file_extension": ".py",
|
|
99
|
+
"mimetype": "text/x-python",
|
|
100
|
+
"name": "python",
|
|
101
|
+
"nbconvert_exporter": "python",
|
|
102
|
+
"pygments_lexer": "ipython2",
|
|
103
|
+
"version": "2.7.6"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"nbformat": 4,
|
|
107
|
+
"nbformat_minor": 5
|
|
108
|
+
}
|