bertrend 0.3.13__tar.gz → 0.3.14__tar.gz
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-0.3.13 → bertrend-0.3.14}/PKG-INFO +10 -4
- {bertrend-0.3.13 → bertrend-0.3.14}/README.md +2 -0
- bertrend-0.3.14/bertrend/article_scoring/article_scoring.py +326 -0
- bertrend-0.3.14/bertrend/article_scoring/prompts.py +143 -0
- bertrend-0.3.14/bertrend/article_scoring/scoring_agent.py +55 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/demos_utils/icons.py +1 -0
- bertrend-0.3.14/bertrend/demos/streamlit_components/__init__.py +0 -0
- bertrend-0.3.14/bertrend/demos/streamlit_components/input_with_pills_components.py +83 -0
- bertrend-0.3.14/bertrend/llm_utils/agent_utils.py +391 -0
- bertrend-0.3.14/bertrend/tests/test_agent_utils.py +443 -0
- bertrend-0.3.14/bertrend/tests/test_article_scoring.py +544 -0
- bertrend-0.3.14/bertrend/tests/test_article_scoring_models.py +389 -0
- bertrend-0.3.14/bertrend/tests/test_mail_utils.py +468 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/common/mail_utils.py +5 -5
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/feeds/decarbonation_feed.toml +3 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/feeds/nlp_feed.toml +3 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/data_provider/__main__.py +23 -1
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/data_provider/data_provider.py +41 -2
- bertrend-0.3.14/bertrend_apps/newsletters/__init__.py +4 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/newsletters/__main__.py +7 -1
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/dashboard_common.py +1 -1
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/detailed_report_template.html +2 -2
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/feeds_config.py +39 -1
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/i18n_translations.py +18 -2
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/models_info.py +11 -6
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/report_generation.py +9 -4
- {bertrend-0.3.13 → bertrend-0.3.14}/pyproject.toml +5 -3
- bertrend-0.3.13/bertrend_apps/config/gmail_credentials.json +0 -1
- {bertrend-0.3.13 → bertrend-0.3.14}/AUTHORS.txt +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/LICENSE.md +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/BERTopicModel.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/BERTrend.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/__init__.py +0 -0
- {bertrend-0.3.13/bertrend_apps/newsletters → bertrend-0.3.14/bertrend/article_scoring}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/config/bertopic_default_config.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/config/bertrend_default_config.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/config/parameters.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/config/services_default_config.toml +0 -0
- {bertrend-0.3.13/bertrend_apps/exploration/curebot → bertrend-0.3.14/bertrend/demos}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/demos_utils/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/demos_utils/data_loading_component.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/demos_utils/embed_documents_component.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/demos_utils/i18n.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/demos_utils/i18n_translations.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/demos_utils/parameters_component.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/demos_utils/state_utils.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/start_summarizer_app.sh +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/start_topic_analysis_app.sh +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/start_weak_signals_app.sh +0 -0
- /bertrend-0.3.13/bertrend_apps/prospective_demo/streamlit_utils.py → /bertrend-0.3.14/bertrend/demos/streamlit_components/clickable_df_component.py +0 -0
- {bertrend-0.3.13/bertrend_apps/exploration → bertrend-0.3.14/bertrend/demos/summarization}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/summarization/summarizer_app.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/topic_analysis/.streamlit/config.toml +0 -0
- {bertrend-0.3.13/bertrend_apps/common → bertrend-0.3.14/bertrend/demos/topic_analysis}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/topic_analysis/app.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/topic_analysis/app_utils.py +0 -0
- {bertrend-0.3.13/bertrend_apps → bertrend-0.3.14/bertrend/demos/topic_analysis/demo_pages}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/topic_analysis/demo_pages/explore_topics.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/topic_analysis/demo_pages/newsletters_generation.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/topic_analysis/demo_pages/temporal_visualizations.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/topic_analysis/demo_pages/topic_visualizations.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/topic_analysis/demo_pages/training_page.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/topic_analysis/i18n_translations.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/weak_signals/.streamlit/config.toml +0 -0
- {bertrend-0.3.13/bertrend/trend_analysis → bertrend-0.3.14/bertrend/demos/weak_signals}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/weak_signals/app.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/weak_signals/i18n_translations.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/demos/weak_signals/visualizations_utils.py +0 -0
- {bertrend-0.3.13/bertrend/topic_analysis → bertrend-0.3.14/bertrend/llm_utils}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/llm_utils/newsletter.css +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/llm_utils/newsletter_features.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/llm_utils/newsletter_model.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/llm_utils/newsletter_outlook_template.html +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/llm_utils/newsletter_template.html +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/llm_utils/openai_client.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/llm_utils/prompts.py +0 -0
- {bertrend-0.3.13/bertrend/tests → bertrend-0.3.14/bertrend/metrics}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/metrics/temporal_metrics_embedding.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/metrics/topic_metrics.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/resources/common_ngrams.json +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/resources/stopwords-en.json +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/resources/stopwords-fr.json +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/resources/stopwords-rte.json +0 -0
- {bertrend-0.3.13/bertrend/services/summary → bertrend-0.3.14/bertrend/services}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/authentication.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_client.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/README.md +0 -0
- {bertrend-0.3.13/bertrend/services/embedding_server/routers → bertrend-0.3.14/bertrend/services/embedding_server}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/bertrend_client_registry.json +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/config/default_config.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/config/settings.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/main.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/models.py +0 -0
- {bertrend-0.3.13/bertrend/services/embedding_server → bertrend-0.3.14/bertrend/services/embedding_server/routers}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/routers/authentication.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/routers/embeddings.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/routers/info.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/security.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/start.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/status.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_server/stop.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/embedding_service.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/summarizer.py +0 -0
- {bertrend-0.3.13/bertrend/services → bertrend-0.3.14/bertrend/services/summary}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/summary/abstractive_summarizer.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/summary/chatgpt_summarizer.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/summary/extractive_summarizer.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/summary/lexrank.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/services/summary/prompts.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/README_integration_tests.md +0 -0
- {bertrend-0.3.13/bertrend/metrics → bertrend-0.3.14/bertrend/tests}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/conftest.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_abstractive_summarizer.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_bertrend.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_cache_utils.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_chatgpt_summarizer.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_config_utils.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_data_loading.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_data_structure.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_embedding_client.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_extractive_summarizer.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_installation.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_integration.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_newsletter_features.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_newsletter_model.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_openai_client.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_representative_docs.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_summarizer.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_temporal_metrics_embedding.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_topic_description.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_topic_metrics.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_topic_model.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_trend_analysis_data_structure.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_trend_analysis_prompts.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_trend_analysis_visualizations.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_trend_analysis_weak_signals.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_utils_init.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/tests/test_visualizations.py +0 -0
- {bertrend-0.3.13/bertrend/llm_utils → bertrend-0.3.14/bertrend/topic_analysis}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/topic_analysis/data_structure.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/topic_analysis/prompts.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/topic_analysis/representative_docs.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/topic_analysis/topic_description.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/topic_analysis/visualizations.py +0 -0
- {bertrend-0.3.13/bertrend/demos/weak_signals → bertrend-0.3.14/bertrend/trend_analysis}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/trend_analysis/data_structure.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/trend_analysis/prompts.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/trend_analysis/signal_llm_template_en.html +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/trend_analysis/signal_llm_template_fr.html +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/trend_analysis/visualizations.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/trend_analysis/weak_signals.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/utils/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/utils/cache_utils.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/utils/config_utils.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend/utils/data_loading.py +0 -0
- {bertrend-0.3.13/bertrend/demos/topic_analysis/demo_pages → bertrend-0.3.14/bertrend_apps}/__init__.py +0 -0
- {bertrend-0.3.13/bertrend/demos/topic_analysis → bertrend-0.3.14/bertrend_apps/common}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/common/crontab_utils.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/feeds/arxiv_feed.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/feeds/llm_en_feed.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/feeds/llm_feed.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/feeds/rte_feed.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/newsletters/arxiv_newsletter_from_last.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/newsletters/decarbonation_newsletter_from_last.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/newsletters/decarbonation_newsletter_from_scratch.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/newsletters/llm_en_newsletter_from_last.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/newsletters/llm_newsletter_from_last.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/newsletters/nlp_newsletter_from_last.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/newsletters/rte_newsletter_from_last.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/config/newsletters/rte_newsletter_from_scratch.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/data_provider/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/data_provider/arxiv_provider.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/data_provider/atom_feed_provider.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/data_provider/bing_news_provider.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/data_provider/google_news_provider.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/data_provider/newscatcher_provider.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/data_provider/utils.py +0 -0
- {bertrend-0.3.13/bertrend/demos/summarization → bertrend-0.3.14/bertrend_apps/exploration}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/curebot/.streamlit/config.toml +0 -0
- {bertrend-0.3.13/bertrend/demos → bertrend-0.3.14/bertrend_apps/exploration/curebot}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/curebot/app.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/curebot/app_utils.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/curebot/config.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/curebot/import_extraction_article.ipynb +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/curebot/prompts.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/curebot/start_newsletter_generator.sh +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/curebot/tabs/tab1.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/curebot/tabs/tab2.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/curebot/tabs/tab3.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/dataviz/Main_page.py +0 -0
- {bertrend-0.3.13/bertrend_apps/exploration/geolocalization/spacy → bertrend-0.3.14/bertrend_apps/exploration/dataviz}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/dataviz/pages/1_Data_feeds.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/geolocalization/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/geolocalization/entity_utils.py +0 -0
- {bertrend-0.3.13/bertrend_apps/exploration/dataviz → bertrend-0.3.14/bertrend_apps/exploration/geolocalization/spacy}/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/geolocalization/spacy/common_factory.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/geolocalization/spacy/dbpedia.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/geolocalization/spacy/nlp.config +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/exploration/geolocalization/spacy/utils.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/install_feeds_and_newsletters.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/.streamlit/config.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/.streamlit/secrets.toml +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/__init__.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/app.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/authentication.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/dashboard_analysis.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/dashboard_signals.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/data_model.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/feeds_common.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/feeds_data.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/llm_utils.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/perf_utils.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/process_new_data.py +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/prospective_demo/start_demo.sh +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/start_apps.sh +0 -0
- {bertrend-0.3.13 → bertrend-0.3.14}/bertrend_apps/stop_apps.sh +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: bertrend
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.14
|
|
4
4
|
Summary: BERTrend analyses topic evolution over time using state-of-the-art transformer models: topic modelling and LLM
|
|
5
5
|
License: LICENSE.md
|
|
6
|
+
License-File: AUTHORS.txt
|
|
7
|
+
License-File: LICENSE.md
|
|
6
8
|
Keywords: topics,BERTopic,transformers,weak signals,LLM
|
|
7
9
|
Author: Jérôme Picault
|
|
8
10
|
Author-email: jerome.picault@rte-france.com
|
|
@@ -42,14 +44,17 @@ Requires-Dist: newspaper4k (>=0.9.3.1) ; extra == "apps"
|
|
|
42
44
|
Requires-Dist: nltk (>=3.9.1)
|
|
43
45
|
Requires-Dist: numpy (<2)
|
|
44
46
|
Requires-Dist: openai (>=1.77.0)
|
|
47
|
+
Requires-Dist: openai-agents[litellm] (>=0.2.5)
|
|
45
48
|
Requires-Dist: openpyxl (>=3.1.5)
|
|
46
49
|
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc (==1.25.0)
|
|
47
50
|
Requires-Dist: pandas (>=2.2.2)
|
|
48
51
|
Requires-Dist: plotly (>=5.24.1)
|
|
49
52
|
Requires-Dist: plotly-resampler (>=0.10.0)
|
|
53
|
+
Requires-Dist: pyarrow (>=21.0.0)
|
|
50
54
|
Requires-Dist: pygooglenews (>=0.1.3) ; extra == "apps"
|
|
51
55
|
Requires-Dist: pylabeladjust (==0.1.13)
|
|
52
56
|
Requires-Dist: pytest (>=8.4.0)
|
|
57
|
+
Requires-Dist: pytest-asyncio
|
|
53
58
|
Requires-Dist: requests (>=2.32.3)
|
|
54
59
|
Requires-Dist: scikit-learn (>=1.5.2)
|
|
55
60
|
Requires-Dist: scipy (==1.12)
|
|
@@ -57,9 +62,8 @@ Requires-Dist: seaborn (>=0.13.2)
|
|
|
57
62
|
Requires-Dist: sentence-transformers (==3.0.1)
|
|
58
63
|
Requires-Dist: sentencepiece (>=0.2.0)
|
|
59
64
|
Requires-Dist: starlette (>=0.46.2)
|
|
60
|
-
Requires-Dist: streamlit (>=1.
|
|
65
|
+
Requires-Dist: streamlit (>=1.48)
|
|
61
66
|
Requires-Dist: streamlit-code-editor (>=0.1.22)
|
|
62
|
-
Requires-Dist: streamlit-tags
|
|
63
67
|
Requires-Dist: supervisor (>=4.2.5)
|
|
64
68
|
Requires-Dist: thefuzz (>=0.22.1)
|
|
65
69
|
Requires-Dist: tiktoken (>=0.8.0)
|
|
@@ -229,6 +233,8 @@ Recommended public datasets:
|
|
|
229
233
|
|
|
230
234
|
For custom dataset creation, please refer to the documentation page about [data providers](./docs/data_provider.md).
|
|
231
235
|
|
|
236
|
+
For email configuration and newsletter sending (optional), please refer to the [mail configuration guide](./docs/mail_configuration.md).
|
|
237
|
+
|
|
232
238
|
|
|
233
239
|
In order to use the provided demos, datasets :
|
|
234
240
|
- can be stored on the server in: `$BERTREND_BASE_DIR/data/bertrend/`
|
|
@@ -152,6 +152,8 @@ Recommended public datasets:
|
|
|
152
152
|
|
|
153
153
|
For custom dataset creation, please refer to the documentation page about [data providers](./docs/data_provider.md).
|
|
154
154
|
|
|
155
|
+
For email configuration and newsletter sending (optional), please refer to the [mail configuration guide](./docs/mail_configuration.md).
|
|
156
|
+
|
|
155
157
|
|
|
156
158
|
In order to use the provided demos, datasets :
|
|
157
159
|
- can be stored on the server in: `$BERTREND_BASE_DIR/data/bertrend/`
|
|
@@ -0,0 +1,326 @@
|
|
|
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
|
+
import json
|
|
6
|
+
from functools import total_ordering
|
|
7
|
+
|
|
8
|
+
from pydantic import (
|
|
9
|
+
BaseModel,
|
|
10
|
+
Field,
|
|
11
|
+
model_validator,
|
|
12
|
+
ConfigDict,
|
|
13
|
+
computed_field,
|
|
14
|
+
model_serializer,
|
|
15
|
+
)
|
|
16
|
+
from typing import Optional, List, Dict, Any
|
|
17
|
+
from enum import Enum
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@total_ordering
|
|
21
|
+
class QualityLevel(Enum):
|
|
22
|
+
"""Quality level categories based on score ranges"""
|
|
23
|
+
|
|
24
|
+
POOR = "Poor" # (0.0-0.25)
|
|
25
|
+
FAIR = "Fair" # (0.25-0.50)
|
|
26
|
+
AVERAGE = "Average" # (0.50-0.65)
|
|
27
|
+
GOOD = "Good" # (0.65-0.8)
|
|
28
|
+
EXCELLENT = "Excellent" # (0.8-1.0)
|
|
29
|
+
|
|
30
|
+
def __lt__(self, other):
|
|
31
|
+
if not isinstance(other, QualityLevel):
|
|
32
|
+
return NotImplemented
|
|
33
|
+
return self.index < other.index
|
|
34
|
+
|
|
35
|
+
def __eq__(self, other):
|
|
36
|
+
if not isinstance(other, QualityLevel):
|
|
37
|
+
return NotImplemented
|
|
38
|
+
return self.name == other.name
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_string(cls, value):
|
|
42
|
+
"""Create a QualityLevel from a string (case-insensitive)"""
|
|
43
|
+
if isinstance(value, cls):
|
|
44
|
+
return value
|
|
45
|
+
|
|
46
|
+
value_lower = str(value).lower().strip()
|
|
47
|
+
for level in cls:
|
|
48
|
+
if level.value.lower() == value_lower:
|
|
49
|
+
return level
|
|
50
|
+
|
|
51
|
+
valid_values = [level.value for level in cls]
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"'{value}' is not a valid QualityLevel. Valid values are: {valid_values}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def index(self):
|
|
58
|
+
"""Get the index (position) of this QualityLevel in the enum definition order"""
|
|
59
|
+
return list(QualityLevel).index(self)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class CriteriaScores(BaseModel):
|
|
63
|
+
"""Individual scores for each quality criterion"""
|
|
64
|
+
|
|
65
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
|
66
|
+
|
|
67
|
+
depth_of_reporting: float = Field(
|
|
68
|
+
...,
|
|
69
|
+
ge=0.0,
|
|
70
|
+
le=1.0,
|
|
71
|
+
description="How well the article provides background, context, explanations, and connects to broader trends",
|
|
72
|
+
)
|
|
73
|
+
originality_and_exclusivity: float = Field(
|
|
74
|
+
...,
|
|
75
|
+
ge=0.0,
|
|
76
|
+
le=1.0,
|
|
77
|
+
description="Unique information, exclusive insights, original reporting, or fresh perspectives",
|
|
78
|
+
)
|
|
79
|
+
source_quality_and_transparency: float = Field(
|
|
80
|
+
...,
|
|
81
|
+
ge=0.0,
|
|
82
|
+
le=1.0,
|
|
83
|
+
description="Quality and transparency of sources, credibility, independence, and conflict disclosure",
|
|
84
|
+
)
|
|
85
|
+
accuracy_and_fact_checking_rigor: float = Field(
|
|
86
|
+
...,
|
|
87
|
+
ge=0.0,
|
|
88
|
+
le=1.0,
|
|
89
|
+
description="Precision, verifiability, appropriate evidence strength, and error handling",
|
|
90
|
+
)
|
|
91
|
+
clarity_and_accessibility: float = Field(
|
|
92
|
+
...,
|
|
93
|
+
ge=0.0,
|
|
94
|
+
le=1.0,
|
|
95
|
+
description="Clear communication, logical hierarchy, and accessibility for target audience",
|
|
96
|
+
)
|
|
97
|
+
balance_and_fairness: float = Field(
|
|
98
|
+
...,
|
|
99
|
+
ge=0.0,
|
|
100
|
+
le=1.0,
|
|
101
|
+
description="Fair presentation of viewpoints, absence of bias, acknowledgment of uncertainties",
|
|
102
|
+
)
|
|
103
|
+
narrative_and_engagement: float = Field(
|
|
104
|
+
...,
|
|
105
|
+
ge=0.0,
|
|
106
|
+
le=1.0,
|
|
107
|
+
description="Reader engagement, storytelling effectiveness, and balance with journalistic integrity",
|
|
108
|
+
)
|
|
109
|
+
timeliness_and_relevance: float = Field(
|
|
110
|
+
...,
|
|
111
|
+
ge=0.0,
|
|
112
|
+
le=1.0,
|
|
113
|
+
description="Currency of information, relevance to current context, and appropriate timing",
|
|
114
|
+
)
|
|
115
|
+
ethical_considerations_and_sensitivity: float = Field(
|
|
116
|
+
...,
|
|
117
|
+
ge=0.0,
|
|
118
|
+
le=1.0,
|
|
119
|
+
description="Ethical reporting standards, sensitivity to affected populations, avoiding sensationalism",
|
|
120
|
+
)
|
|
121
|
+
rte_relevance_and_strategic_impact: float = Field(
|
|
122
|
+
...,
|
|
123
|
+
ge=0.0,
|
|
124
|
+
le=1.0,
|
|
125
|
+
description="Relevance to RTE operations, energy transmission, grid management, and strategic planning",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def get_criterion_names(cls) -> List[str]:
|
|
130
|
+
"""Get list of all criterion names"""
|
|
131
|
+
return list(cls.model_fields.keys())
|
|
132
|
+
|
|
133
|
+
def to_dict(self) -> Dict[str, float]:
|
|
134
|
+
"""Convert scores to dictionary"""
|
|
135
|
+
return {name: getattr(self, name) for name in self.get_criterion_names()}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class WeightConfig(BaseModel):
|
|
139
|
+
"""Configurable weights for each criterion"""
|
|
140
|
+
|
|
141
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
|
142
|
+
depth_of_reporting: float = Field(0.15, ge=0.0, le=1.0)
|
|
143
|
+
originality_and_exclusivity: float = Field(0.10, ge=0.0, le=1.0)
|
|
144
|
+
source_quality_and_transparency: float = Field(0.15, ge=0.0, le=1.0)
|
|
145
|
+
accuracy_and_fact_checking_rigor: float = Field(0.15, ge=0.0, le=1.0)
|
|
146
|
+
clarity_and_accessibility: float = Field(0.10, ge=0.0, le=1.0)
|
|
147
|
+
balance_and_fairness: float = Field(0.10, ge=0.0, le=1.0)
|
|
148
|
+
narrative_and_engagement: float = Field(0.10, ge=0.0, le=1.0)
|
|
149
|
+
timeliness_and_relevance: float = Field(0.05, ge=0.0, le=1.0)
|
|
150
|
+
ethical_considerations_and_sensitivity: float = Field(0.05, ge=0.0, le=1.0)
|
|
151
|
+
rte_relevance_and_strategic_impact: float = Field(0.05, ge=0.0, le=1.0)
|
|
152
|
+
|
|
153
|
+
@model_validator(mode="after")
|
|
154
|
+
def weights_validator(self) -> "WeightConfig":
|
|
155
|
+
"""Ensure all weights sum to less than the number of criteria"""
|
|
156
|
+
total = sum(
|
|
157
|
+
getattr(self, name) for name in CriteriaScores.get_criterion_names()
|
|
158
|
+
)
|
|
159
|
+
if not total <= len(CriteriaScores.get_criterion_names()):
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"Weights must not exceed {len(CriteriaScores.get_criterion_names())}, got {total:.6f}"
|
|
162
|
+
)
|
|
163
|
+
return self
|
|
164
|
+
|
|
165
|
+
def to_dict(self) -> Dict[str, float]:
|
|
166
|
+
"""Convert weights to dictionary"""
|
|
167
|
+
return {
|
|
168
|
+
name: getattr(self, name) for name in CriteriaScores.get_criterion_names()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ArticleScore(BaseModel):
|
|
173
|
+
"""Complete article scoring with individual scores and final result"""
|
|
174
|
+
|
|
175
|
+
model_config = ConfigDict(
|
|
176
|
+
validate_assignment=True,
|
|
177
|
+
extra="forbid",
|
|
178
|
+
arbitrary_types_allowed=True,
|
|
179
|
+
use_enum_values=True, # to serialize enum values
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Required fields
|
|
183
|
+
scores: CriteriaScores
|
|
184
|
+
|
|
185
|
+
# Optional configuration
|
|
186
|
+
weights: WeightConfig = Field(default_factory=WeightConfig)
|
|
187
|
+
|
|
188
|
+
# Assessment metadata
|
|
189
|
+
assessment_summary: Optional[str] = Field(
|
|
190
|
+
None, description="Brief summary of strengths and weaknesses"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
@computed_field
|
|
194
|
+
@property
|
|
195
|
+
def final_score(self) -> float:
|
|
196
|
+
"""Calculate weighted final score"""
|
|
197
|
+
score_dict = self.scores.to_dict()
|
|
198
|
+
weight_dict = self.weights.to_dict()
|
|
199
|
+
|
|
200
|
+
weighted_sum = sum(
|
|
201
|
+
score_dict[name] * weight_dict[name] for name in score_dict.keys()
|
|
202
|
+
)
|
|
203
|
+
return round(weighted_sum, 3) / len(score_dict)
|
|
204
|
+
|
|
205
|
+
@computed_field
|
|
206
|
+
@property
|
|
207
|
+
def quality_level(self) -> QualityLevel:
|
|
208
|
+
"""Determine quality level based on final score"""
|
|
209
|
+
score = self.final_score
|
|
210
|
+
if 0.0 <= score < 0.25:
|
|
211
|
+
return QualityLevel.POOR
|
|
212
|
+
elif 0.25 <= score < 0.50:
|
|
213
|
+
return QualityLevel.FAIR
|
|
214
|
+
elif 0.5 <= score < 0.65:
|
|
215
|
+
return QualityLevel.AVERAGE
|
|
216
|
+
elif 0.65 <= score < 0.8:
|
|
217
|
+
return QualityLevel.GOOD
|
|
218
|
+
else: # 0.8 <= score <= 1.0
|
|
219
|
+
return QualityLevel.EXCELLENT
|
|
220
|
+
|
|
221
|
+
def get_detailed_breakdown(self) -> Dict[str, Dict[str, float]]:
|
|
222
|
+
"""Get detailed score breakdown with weights and contributions"""
|
|
223
|
+
score_dict = self.scores.to_dict()
|
|
224
|
+
weight_dict = self.weights.to_dict()
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
criterion: {
|
|
228
|
+
"score": score_dict[criterion],
|
|
229
|
+
"weight": weight_dict[criterion],
|
|
230
|
+
"weighted_contribution": round(
|
|
231
|
+
score_dict[criterion] * weight_dict[criterion], 3
|
|
232
|
+
),
|
|
233
|
+
}
|
|
234
|
+
for criterion in score_dict.keys()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
def get_top_strengths(self, n: int = 3) -> List[tuple[str, float]]:
|
|
238
|
+
"""Get top N scoring criteria"""
|
|
239
|
+
score_dict = self.scores.to_dict()
|
|
240
|
+
sorted_scores = sorted(score_dict.items(), key=lambda x: x[1], reverse=True)
|
|
241
|
+
return sorted_scores[:n]
|
|
242
|
+
|
|
243
|
+
def get_top_weaknesses(self, n: int = 3) -> List[tuple[str, float]]:
|
|
244
|
+
"""Get bottom N scoring criteria"""
|
|
245
|
+
score_dict = self.scores.to_dict()
|
|
246
|
+
sorted_scores = sorted(score_dict.items(), key=lambda x: x[1])
|
|
247
|
+
return sorted_scores[:n]
|
|
248
|
+
|
|
249
|
+
def to_report(self, include_breakdown: bool = True) -> str:
|
|
250
|
+
"""Generate a formatted assessment report"""
|
|
251
|
+
report_lines = [
|
|
252
|
+
"ARTICLE QUALITY ASSESSMENT REPORT",
|
|
253
|
+
"=" * 50,
|
|
254
|
+
"",
|
|
255
|
+
f"FINAL SCORE: {self.final_score}/1.0 ({self.quality_level.value})",
|
|
256
|
+
"",
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
if include_breakdown:
|
|
260
|
+
report_lines.append("INDIVIDUAL SCORES:")
|
|
261
|
+
breakdown = self.get_detailed_breakdown()
|
|
262
|
+
for i, (criterion, data) in enumerate(breakdown.items(), 1):
|
|
263
|
+
criterion_name = criterion.replace("_", " ").title()
|
|
264
|
+
report_lines.append(
|
|
265
|
+
f"{i:2d}. {criterion_name}: {data['score']:.2f}/1.0 "
|
|
266
|
+
f"(Weight: {data['weight']:.0%}, Contribution: {data['weighted_contribution']:.3f})"
|
|
267
|
+
)
|
|
268
|
+
report_lines.append("")
|
|
269
|
+
|
|
270
|
+
# Add strengths and weaknesses
|
|
271
|
+
strengths = self.get_top_strengths(3)
|
|
272
|
+
weaknesses = self.get_top_weaknesses(3)
|
|
273
|
+
|
|
274
|
+
report_lines.extend(
|
|
275
|
+
[
|
|
276
|
+
"TOP STRENGTHS:",
|
|
277
|
+
*[
|
|
278
|
+
f"• {name.replace('_', ' ').title()}: {score:.2f}"
|
|
279
|
+
for name, score in strengths
|
|
280
|
+
],
|
|
281
|
+
"",
|
|
282
|
+
"AREAS FOR IMPROVEMENT:",
|
|
283
|
+
*[
|
|
284
|
+
f"• {name.replace('_', ' ').title()}: {score:.2f}"
|
|
285
|
+
for name, score in weaknesses
|
|
286
|
+
],
|
|
287
|
+
"",
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if self.assessment_summary:
|
|
292
|
+
report_lines.extend(
|
|
293
|
+
[
|
|
294
|
+
"OVERALL ASSESSMENT:",
|
|
295
|
+
self.assessment_summary,
|
|
296
|
+
"",
|
|
297
|
+
]
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return "\n".join(report_lines)
|
|
301
|
+
|
|
302
|
+
def export_to_dict(self) -> Dict[str, Any]:
|
|
303
|
+
"""Export complete assessment to dictionary for serialization"""
|
|
304
|
+
return {
|
|
305
|
+
"scores": self.scores.model_dump(),
|
|
306
|
+
"weights": self.weights.model_dump(),
|
|
307
|
+
"final_score": self.final_score,
|
|
308
|
+
"quality_level": self.quality_level.value, # Explicitly convert enum to string
|
|
309
|
+
"assessment_summary": self.assessment_summary,
|
|
310
|
+
"detailed_breakdown": self.get_detailed_breakdown(),
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
def to_json(self) -> str:
|
|
314
|
+
"""Export to JSON string"""
|
|
315
|
+
return json.dumps(self.export_to_dict(), indent=2)
|
|
316
|
+
|
|
317
|
+
@model_serializer
|
|
318
|
+
def serialize_model(self) -> Dict[str, Any]:
|
|
319
|
+
"""Custom serializer to handle QualityLevel enum"""
|
|
320
|
+
return {
|
|
321
|
+
"scores": self.scores.model_dump(),
|
|
322
|
+
"weights": self.weights.model_dump(),
|
|
323
|
+
"final_score": self.final_score,
|
|
324
|
+
"quality_level": self.quality_level.value,
|
|
325
|
+
"assessment_summary": self.assessment_summary,
|
|
326
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
ARTICLE_SCORING_PROMPT = """
|
|
7
|
+
# News Article Quality Assessment Prompt
|
|
8
|
+
|
|
9
|
+
You are an expert news analyst tasked with evaluating the quality of a news article. Please assess the article across 10 key criteria score between 0 (poor) and 1 (excellent).
|
|
10
|
+
|
|
11
|
+
## Assessment Instructions
|
|
12
|
+
|
|
13
|
+
For each criterion below, assign a score from 0 to 1 based on how well the article meets the specified requirements.
|
|
14
|
+
|
|
15
|
+
### Scoring Scale:
|
|
16
|
+
- **0.0-0.25**: Poor - Fails to meet basic standards
|
|
17
|
+
- **0.25-0.5**: Fair - Meets some requirements but has significant gaps
|
|
18
|
+
- **0.5-0.65**: Average - Meets most basic requirements
|
|
19
|
+
- **0.65-0.8**: Good - Exceeds expectations in most areas
|
|
20
|
+
- **0.8-1.0**: Excellent - Exceptional quality, sets the standard
|
|
21
|
+
|
|
22
|
+
## Evaluation Criteria
|
|
23
|
+
|
|
24
|
+
### 1. Depth of Reporting
|
|
25
|
+
**Score: ___/1.0**
|
|
26
|
+
|
|
27
|
+
Evaluate:
|
|
28
|
+
- How well does the article provide background, context, and explanations?
|
|
29
|
+
- Does it go beyond surface facts to explain why or how?
|
|
30
|
+
- Does it cover implications and consequences?
|
|
31
|
+
- Are expert opinions or analysis included?
|
|
32
|
+
- Does it connect current events to broader trends or historical patterns?
|
|
33
|
+
|
|
34
|
+
### 2. Originality and Exclusivity
|
|
35
|
+
**Score: ___/1.0**
|
|
36
|
+
|
|
37
|
+
Evaluate:
|
|
38
|
+
- Does the article provide unique information or exclusive insights not found elsewhere?
|
|
39
|
+
- Are there original interviews, data, or on-the-ground reporting?
|
|
40
|
+
- Does it offer a fresh angle or perspective on a familiar story?
|
|
41
|
+
|
|
42
|
+
### 3. Source Quality and Transparency
|
|
43
|
+
**Score: ___/1.0**
|
|
44
|
+
|
|
45
|
+
Evaluate:
|
|
46
|
+
- Are sources explicitly named and credible (experts, official data, witnesses)?
|
|
47
|
+
- Are multiple, independent sources cited to avoid reliance on a single perspective?
|
|
48
|
+
- Is there a clear distinction between primary and secondary sources?
|
|
49
|
+
- Are potential conflicts of interest disclosed?
|
|
50
|
+
|
|
51
|
+
### 4. Accuracy and Fact-Checking Rigor
|
|
52
|
+
**Score: ___/1.0**
|
|
53
|
+
|
|
54
|
+
Evaluate:
|
|
55
|
+
- Is every fact precise and verifiable?
|
|
56
|
+
- Are statistics and data presented with references or links?
|
|
57
|
+
- Are potential errors or corrections addressed openly?
|
|
58
|
+
- Are claims substantiated with appropriate evidence strength (avoiding over-interpretation of preliminary data)?
|
|
59
|
+
|
|
60
|
+
### 5. Clarity and Accessibility
|
|
61
|
+
**Score: ___/1.0**
|
|
62
|
+
|
|
63
|
+
Evaluate:
|
|
64
|
+
- Is complex information broken down clearly for the target audience?
|
|
65
|
+
- Does the article avoid ambiguity or confusion?
|
|
66
|
+
- Are technical terms well defined or minimized?
|
|
67
|
+
- Is the information hierarchy logical and easy to follow?
|
|
68
|
+
|
|
69
|
+
### 6. Balance and Fairness
|
|
70
|
+
**Score: ___/1.0**
|
|
71
|
+
|
|
72
|
+
Evaluate:
|
|
73
|
+
- Does the article fairly present conflicting viewpoints or relevant nuances?
|
|
74
|
+
- Is it free from hidden biases or agenda-driven language?
|
|
75
|
+
- Does it acknowledge uncertainties and limitations in the information presented?
|
|
76
|
+
|
|
77
|
+
### 7. Narrative and Engagement
|
|
78
|
+
**Score: ___/1.0**
|
|
79
|
+
|
|
80
|
+
Evaluate:
|
|
81
|
+
- How well does the article maintain reader interest?
|
|
82
|
+
- Does it use storytelling, vivid examples, or quotes effectively?
|
|
83
|
+
- Is the headline catchy but truthful?
|
|
84
|
+
- Does it strike the right balance between engagement and journalistic integrity?
|
|
85
|
+
|
|
86
|
+
### 8. Timeliness and Relevance
|
|
87
|
+
**Score: ___/1.0**
|
|
88
|
+
|
|
89
|
+
Evaluate:
|
|
90
|
+
- Is the article up-to-date with the latest developments?
|
|
91
|
+
- Does it highlight why the topic matters now?
|
|
92
|
+
- Does it provide appropriate context about what has changed since previous reporting?
|
|
93
|
+
|
|
94
|
+
### 9. Ethical Considerations and Sensitivity
|
|
95
|
+
**Score: ___/1.0**
|
|
96
|
+
|
|
97
|
+
Evaluate:
|
|
98
|
+
- Does the article avoid sensationalism or fearmongering?
|
|
99
|
+
- Does it respect privacy and avoid harm?
|
|
100
|
+
- Does it consider the potential impact on vulnerable populations or communities mentioned?
|
|
101
|
+
|
|
102
|
+
### 10. RTE Relevance and Strategic Impact
|
|
103
|
+
**Score: ___/1.0**
|
|
104
|
+
|
|
105
|
+
Evaluate:
|
|
106
|
+
- Does the article address issues directly impacting electricity transmission, grid management, or energy infrastructure?
|
|
107
|
+
- Are there implications for French energy security, grid stability, or electricity market operations?
|
|
108
|
+
- Does it cover regulatory changes, policy decisions, or geopolitical events affecting energy supply chains?
|
|
109
|
+
- Are there connections to renewable energy integration, nuclear policy, or European energy interconnections?
|
|
110
|
+
- Does it address energy transition challenges relevant to transmission system operations?
|
|
111
|
+
- Are there economic, political, or environmental factors that could influence RTE's strategic planning or operations?
|
|
112
|
+
|
|
113
|
+
## Assessment Output Format
|
|
114
|
+
|
|
115
|
+
Please provide your assessment in the following format:
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
**Article Title:** [Insert title]
|
|
120
|
+
|
|
121
|
+
**Individual Scores:**
|
|
122
|
+
1. Depth of Reporting: ___
|
|
123
|
+
2. Originality and Exclusivity: ___
|
|
124
|
+
3. Source Quality and Transparency: ___
|
|
125
|
+
4. Accuracy and Fact-Checking Rigor: ___
|
|
126
|
+
5. Clarity and Accessibility: ___
|
|
127
|
+
6. Balance and Fairness: ___
|
|
128
|
+
7. Narrative and Engagement: ___
|
|
129
|
+
8. Timeliness and Relevance: ___
|
|
130
|
+
9. Ethical Considerations and Sensitivity: ___
|
|
131
|
+
10. RTE Relevance and Strategic Impact: ___
|
|
132
|
+
|
|
133
|
+
**Overall Assessment:** [Brief summary of strengths and weaknesses]
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Important Notes:
|
|
138
|
+
- Consider the article type (breaking news, investigative, opinion, analysis) when evaluating
|
|
139
|
+
- Weight journalistic fundamentals (accuracy, sources, ethics) more heavily than stylistic elements
|
|
140
|
+
- Be objective and evidence-based in your scoring
|
|
141
|
+
- Provide specific examples from the article to support your scores
|
|
142
|
+
|
|
143
|
+
"""
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from agents import ModelSettings
|
|
5
|
+
|
|
6
|
+
from bertrend.article_scoring.article_scoring import ArticleScore
|
|
7
|
+
from bertrend.article_scoring.prompts import ARTICLE_SCORING_PROMPT
|
|
8
|
+
from bertrend.llm_utils.agent_utils import (
|
|
9
|
+
BaseAgentFactory,
|
|
10
|
+
AsyncAgentConcurrentProcessor,
|
|
11
|
+
progress_reporter,
|
|
12
|
+
)
|
|
13
|
+
from bertrend.utils.data_loading import load_data
|
|
14
|
+
|
|
15
|
+
DEFAULT_CHUNK_SIZE = 25
|
|
16
|
+
DEFAULT_MAX_CONCURRENT_TASKS = 25
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def score_articles(articles: list[str]):
|
|
20
|
+
agent = BaseAgentFactory().create_agent(
|
|
21
|
+
name="scoring_agent",
|
|
22
|
+
instructions=ARTICLE_SCORING_PROMPT,
|
|
23
|
+
output_type=ArticleScore,
|
|
24
|
+
model_settings=ModelSettings(temperature=0.1),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Initialize processor
|
|
28
|
+
processor = AsyncAgentConcurrentProcessor(
|
|
29
|
+
agent=agent, max_concurrent=DEFAULT_MAX_CONCURRENT_TASKS
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Uncomment to process multiple items:
|
|
33
|
+
results = await processor.process_list_concurrent(
|
|
34
|
+
articles, progress_callback=progress_reporter, chunk_size=DEFAULT_CHUNK_SIZE
|
|
35
|
+
)
|
|
36
|
+
return results
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
|
|
41
|
+
path = Path("/DSIA/nlp/bertrend/data/feeds/feed_nlp/2025-01-07_feed_nlp.jsonl")
|
|
42
|
+
df = load_data(path)
|
|
43
|
+
print(len(df), df.columns)
|
|
44
|
+
l = list(df.text)
|
|
45
|
+
results = asyncio.run(score_articles(l))
|
|
46
|
+
|
|
47
|
+
assert len(results) == len(df)
|
|
48
|
+
df["quality_metrics"] = [r.output if not r.error else None for r in results]
|
|
49
|
+
df["overall_quality"] = df["quality_metrics"].apply(
|
|
50
|
+
lambda x: x.quality_level.name if x else None
|
|
51
|
+
)
|
|
52
|
+
print(df.overall_quality)
|
|
53
|
+
category_percentages = df["overall_quality"].value_counts(normalize=True) * 100
|
|
54
|
+
# Display percentages
|
|
55
|
+
print(category_percentages)
|
|
@@ -34,6 +34,7 @@ STRONG_SIGNAL_ICON = ":material/signal_cellular_3_bar:"
|
|
|
34
34
|
DOWNLOAD_ICON = ":material/download:"
|
|
35
35
|
EMAIL_ICON = ":material/mail:"
|
|
36
36
|
RESTART_ICON = ":material/restart_alt:"
|
|
37
|
+
PERSON_ADD_ICON = ":material/person_add:"
|
|
37
38
|
|
|
38
39
|
JSON_ICON = "🧾"
|
|
39
40
|
PARQUET_ICON = "📦️"
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import streamlit as st
|
|
2
|
+
from typing import Callable, Optional, List
|
|
3
|
+
|
|
4
|
+
from bertrend.demos.demos_utils.icons import PERSON_ADD_ICON
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@st.fragment
|
|
8
|
+
def input_with_pills(
|
|
9
|
+
label: str,
|
|
10
|
+
key: str,
|
|
11
|
+
placeholder: str = "",
|
|
12
|
+
validate_fn: Optional[Callable[[str], bool]] = None,
|
|
13
|
+
label_visibility="hidden",
|
|
14
|
+
) -> List[str]:
|
|
15
|
+
"""
|
|
16
|
+
A simple Streamlit component that auto-adds valid input to pills.
|
|
17
|
+
Dialog-safe version that doesn't use st.rerun()
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# Initialize session state
|
|
21
|
+
all_items_key = f"{key}_all_items"
|
|
22
|
+
selected_items_key = f"{key}_selected_items"
|
|
23
|
+
input_key = f"{key}_input"
|
|
24
|
+
|
|
25
|
+
if all_items_key not in st.session_state:
|
|
26
|
+
st.session_state[all_items_key] = []
|
|
27
|
+
if selected_items_key not in st.session_state:
|
|
28
|
+
st.session_state[selected_items_key] = []
|
|
29
|
+
|
|
30
|
+
# Create columns: input on left, pills on right
|
|
31
|
+
col1, col2 = st.columns([3, 3])
|
|
32
|
+
|
|
33
|
+
with col1:
|
|
34
|
+
# Create a form to handle Enter key press without rerun
|
|
35
|
+
with st.form(key=f"{key}_form", clear_on_submit=True, border=False):
|
|
36
|
+
current_input = st.text_input(
|
|
37
|
+
label,
|
|
38
|
+
placeholder=placeholder,
|
|
39
|
+
key=input_key,
|
|
40
|
+
label_visibility=label_visibility,
|
|
41
|
+
)
|
|
42
|
+
# Submit button is required but will be hidden
|
|
43
|
+
submitted = st.form_submit_button(PERSON_ADD_ICON)
|
|
44
|
+
|
|
45
|
+
if submitted and current_input.strip():
|
|
46
|
+
is_valid = True
|
|
47
|
+
error_msg = ""
|
|
48
|
+
|
|
49
|
+
if validate_fn and not validate_fn(current_input.strip()):
|
|
50
|
+
is_valid = False
|
|
51
|
+
error_msg = "Invalid format"
|
|
52
|
+
elif current_input.strip() in st.session_state[all_items_key]:
|
|
53
|
+
is_valid = False
|
|
54
|
+
error_msg = "Already exists"
|
|
55
|
+
|
|
56
|
+
if is_valid:
|
|
57
|
+
# Add to both lists
|
|
58
|
+
st.session_state[all_items_key].append(current_input.strip())
|
|
59
|
+
st.session_state[selected_items_key].append(current_input.strip())
|
|
60
|
+
else:
|
|
61
|
+
st.error(error_msg)
|
|
62
|
+
|
|
63
|
+
with col2:
|
|
64
|
+
# Display pills
|
|
65
|
+
if st.session_state[all_items_key]:
|
|
66
|
+
st.markdown("<div style='margin-top: 8px;'></div>", unsafe_allow_html=True)
|
|
67
|
+
|
|
68
|
+
# Use a callback to handle selection changes
|
|
69
|
+
def on_pills_change():
|
|
70
|
+
selected = st.session_state[f"{key}_pills_widget"]
|
|
71
|
+
st.session_state[selected_items_key] = selected
|
|
72
|
+
|
|
73
|
+
selected_pills = st.pills(
|
|
74
|
+
"Items",
|
|
75
|
+
st.session_state[all_items_key],
|
|
76
|
+
selection_mode="multi",
|
|
77
|
+
key=f"{key}_pills_widget",
|
|
78
|
+
label_visibility="collapsed",
|
|
79
|
+
default=st.session_state[selected_items_key],
|
|
80
|
+
on_change=on_pills_change,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return st.session_state[selected_items_key]
|