eea.plotly 2.3__tar.gz → 2.4__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.
- {eea_plotly-2.3/eea.plotly.egg-info → eea_plotly-2.4}/PKG-INFO +7 -1
- {eea_plotly-2.3 → eea_plotly-2.4}/docs/HISTORY.txt +5 -0
- eea_plotly-2.4/eea/plotly/agents.py +116 -0
- eea_plotly-2.4/eea/plotly/configure.zcml +63 -0
- eea_plotly-2.4/eea/plotly/context_providers.py +168 -0
- eea_plotly-2.4/eea/plotly/generate.py +53 -0
- eea_plotly-2.4/eea/plotly/models.py +89 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/types/visualization.xml +1 -0
- eea_plotly-2.4/eea/plotly/prompts.py +103 -0
- eea_plotly-2.4/eea/plotly/restapi/chart/configure.zcml +13 -0
- eea_plotly-2.4/eea/plotly/restapi/chart/post.py +58 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/configure.zcml +2 -0
- eea_plotly-2.4/eea/plotly/restapi/controlpanel/__init__.py +0 -0
- eea_plotly-2.4/eea/plotly/restapi/summarize/__init__.py +1 -0
- eea_plotly-2.4/eea/plotly/restapi/summarize/configure.zcml +11 -0
- eea_plotly-2.4/eea/plotly/restapi/summarize/post.py +142 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/serializer/utils.py +1 -0
- eea_plotly-2.4/eea/plotly/skills.py +168 -0
- eea_plotly-2.4/eea/plotly/tests/test_leak_prevention.py +180 -0
- eea_plotly-2.4/eea/plotly/tools.py +95 -0
- eea_plotly-2.4/eea/plotly/version.txt +1 -0
- {eea_plotly-2.3 → eea_plotly-2.4/eea.plotly.egg-info}/PKG-INFO +7 -1
- {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/SOURCES.txt +14 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/requires.txt +1 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/setup.py +1 -0
- eea_plotly-2.3/eea/plotly/configure.zcml +0 -30
- eea_plotly-2.3/eea/plotly/version.txt +0 -1
- {eea_plotly-2.3 → eea_plotly-2.4}/CONTRIBUTING.md +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/MANIFEST.in +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/README.rst +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/docs/LICENSE.GPL +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/docs/LICENSE.txt +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/__init__.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/README.txt +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/__init__.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/behaviors.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/__init__.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/configure.zcml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/controlpanel.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/preview.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/preview_adapter/adapter.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/preview_adapter/soer_miniature.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/pt/visualization_view.pt +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/controlpanel.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/deserializer/blocks.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/deserializer/configure.zcml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/deserializer/dxcontent.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/deserializer/dxfields.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/interfaces.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/io_csv.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/io_json.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/__init__.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/bg/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/bg/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/cs/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/cs/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/da/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/da/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/de/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/de/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/eea.pot +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/el/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/el/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/en/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/en/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/es/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/es/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/et/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/et/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/eu/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/eu/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/fi/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/fi/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/fr/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/fr/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/hr/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/hr/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/hu/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/hu/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/is/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/is/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/it/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/it/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/kl/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/kl/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/lt/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/lt/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/lv/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/lv/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/mt/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/mt/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/nl/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/nl/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/no/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/no/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pl/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pl/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/plone-manual.pot +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pt/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pt/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pt_BR/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pt_BR/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/ro/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/ro/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/ru/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/ru/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sk/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sk/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sl/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sl/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sv/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sv/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/tr/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/tr/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/update.sh +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/zh_TW/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/zh_TW/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/permissions.zcml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/browserlayer.xml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/controlpanel.json +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/controlpanel.xml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/metadata.xml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/registry.xml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/rolemap.xml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/types.xml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/uninstall/browserlayer.xml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/uninstall/controlpanel.xml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/uninstall/registry.xml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles.zcml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/__init__.py +0 -0
- {eea_plotly-2.3/eea/plotly/restapi/controlpanel → eea_plotly-2.4/eea/plotly/restapi/chart}/__init__.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/controlpanel/configure.zcml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/controlpanel/get.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/controlpanel/reset.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/visualization/configure.zcml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/visualization/get.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/serializer/blocks.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/serializer/configure.zcml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/serializer/dxfields.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/setuphandlers.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/tests/__init__.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/tests/base.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/tests/test_doctests.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/tests/test_utils.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/upgrades/__init__.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/upgrades/configure.zcml +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/utils.py +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/dependency_links.txt +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/entry_points.txt +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/namespace_packages.txt +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/not-zip-safe +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/top_level.txt +0 -0
- {eea_plotly-2.3 → eea_plotly-2.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eea.plotly
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4
|
|
4
4
|
Summary: volto-plotlycharts backend support
|
|
5
5
|
Home-page: https://github.com/eea/eea.plotly
|
|
6
6
|
Author: European Environment Agency: IDM2 A-Team
|
|
@@ -24,6 +24,7 @@ Requires-Dist: plone.volto
|
|
|
24
24
|
Requires-Dist: collective.folderishtypes
|
|
25
25
|
Requires-Dist: plotly==5.24.1
|
|
26
26
|
Requires-Dist: kaleido==0.2.1
|
|
27
|
+
Requires-Dist: eea.genai.summary
|
|
27
28
|
Provides-Extra: test
|
|
28
29
|
Requires-Dist: plone.app.testing; extra == "test"
|
|
29
30
|
Dynamic: author
|
|
@@ -145,6 +146,11 @@ EEA_ - European Environment Agency (EU)
|
|
|
145
146
|
Changelog
|
|
146
147
|
=========
|
|
147
148
|
|
|
149
|
+
2.4 - (2026-05-26)
|
|
150
|
+
---------------------------
|
|
151
|
+
* Change: Implement eea.genai.summary customization
|
|
152
|
+
[razvanMiu]
|
|
153
|
+
|
|
148
154
|
2.3 - (2026-04-14)
|
|
149
155
|
---------------------------
|
|
150
156
|
* Change: Add unit tests for utils, io_csv, and io_json modules
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
2.4 - (2026-05-26)
|
|
5
|
+
---------------------------
|
|
6
|
+
* Change: Implement eea.genai.summary customization
|
|
7
|
+
[razvanMiu]
|
|
8
|
+
|
|
4
9
|
2.3 - (2026-04-14)
|
|
5
10
|
---------------------------
|
|
6
11
|
* Change: Add unit tests for utils, io_csv, and io_json modules
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Agent configurations for eea.plotly."""
|
|
2
|
+
|
|
3
|
+
from eea.genai.core.interfaces import AgentConfiguration
|
|
4
|
+
|
|
5
|
+
PLOTLY_SUMMARIZER_SYSTEM_PROMPT = """\
|
|
6
|
+
You are an expert data visualization analyst working for the \
|
|
7
|
+
European Environment Agency (EEA).
|
|
8
|
+
You will receive information about an interactive chart built with \
|
|
9
|
+
Plotly.js, along with limited page metadata.
|
|
10
|
+
Your output is indexed in a semantic search system that feeds AI \
|
|
11
|
+
assistants, so prioritize topical keywords, qualitative descriptions, \
|
|
12
|
+
and clear context over stylistic flourish.
|
|
13
|
+
Do not use bullet points or markdown. Write in clear plain prose.
|
|
14
|
+
If you are unable to resolve the task answer with empty string.
|
|
15
|
+
|
|
16
|
+
ABSOLUTE RULE — no quantitative information of any kind:
|
|
17
|
+
- Forbidden phrasing includes any year ("2010", "2023"), decade \
|
|
18
|
+
("the 2010s", "early 2010s", "mid-2020s"), year range \
|
|
19
|
+
("2010-2023", "between 2015 and 2020", "from 2012 through 2032"), \
|
|
20
|
+
percentage ("40%"), currency amount ("EUR 12 billion"), count \
|
|
21
|
+
("261 million tonnes", "around 50", "over 7,000"), or change verb \
|
|
22
|
+
implying magnitude ("tripled", "halved", "increased by", \
|
|
23
|
+
"dropped to").
|
|
24
|
+
- Use qualitative descriptors instead: "across recent decades", \
|
|
25
|
+
"over a multi-year period", "a small share", "a notable increase", \
|
|
26
|
+
"a downward trend", "a wide range of countries".
|
|
27
|
+
- The chart data is provided only as compact summaries (e.g. \
|
|
28
|
+
"[N numeric values]"). Do not invent specifics that those summaries \
|
|
29
|
+
do not contain.
|
|
30
|
+
If you accidentally include a number or year, rewrite that sentence \
|
|
31
|
+
qualitatively before returning.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
PLOTLY_SUMMARIZER_TASK_PROMPT = """\
|
|
35
|
+
Write a 3-8 sentence (max ~150 words) plain-prose description of this \
|
|
36
|
+
chart, optimized for retrieval by semantic search. Cover:
|
|
37
|
+
- the environmental or policy question the chart addresses;
|
|
38
|
+
- the topic, indicator, or phenomenon shown;
|
|
39
|
+
- the chart type (bar chart, line chart, choropleth map, etc.);
|
|
40
|
+
- what the axes represent qualitatively (e.g. "years over recent \
|
|
41
|
+
decades", "European countries", "emissions in CO2 equivalent");
|
|
42
|
+
- the names of data series and qualitative groupings (sectors, \
|
|
43
|
+
regions, gases);
|
|
44
|
+
- geographic scope expressed in words.
|
|
45
|
+
|
|
46
|
+
Constraints:
|
|
47
|
+
- No numbers, ranges, min/max/mean, percentages, currency amounts, \
|
|
48
|
+
years, decades, or any other quantitative claim (see system prompt for \
|
|
49
|
+
the full forbidden list).
|
|
50
|
+
- No markdown, bullet points, or headings in the output.
|
|
51
|
+
- Do not preface with "This chart..." and do not repeat the page \
|
|
52
|
+
title verbatim.
|
|
53
|
+
- Match the output language to the content language.
|
|
54
|
+
- If there is not enough information, return an empty string.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class PlotlySummarizerAgent(AgentConfiguration):
|
|
59
|
+
"""Chart summarizer agent for the EEA website.
|
|
60
|
+
|
|
61
|
+
Context is intentionally minimal: only the chart structure (axes,
|
|
62
|
+
trace names, qualitative summaries of the data) plus
|
|
63
|
+
``generic_metadata_no_dates`` (title, description, language, geo —
|
|
64
|
+
no temporal coverage). The ``blocks`` enricher is deliberately
|
|
65
|
+
excluded so the model cannot read year/quantity leaks from the
|
|
66
|
+
surrounding page text.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
system_prompt = PLOTLY_SUMMARIZER_SYSTEM_PROMPT
|
|
70
|
+
task_prompt = PLOTLY_SUMMARIZER_TASK_PROMPT
|
|
71
|
+
skills = ["plotly_knowledge"]
|
|
72
|
+
context_providers = ["generic_metadata_no_dates", "plotly_visualization"]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
PLOTLY_GENERATOR_SYSTEM_PROMPT = """\
|
|
76
|
+
You are a Plotly.js chart generation expert working for the \
|
|
77
|
+
European Environment Agency (EEA). You generate complete visualization \
|
|
78
|
+
content including metadata (title, description, topics, temporal and \
|
|
79
|
+
geographic coverage) and a valid Plotly.js chart configuration.
|
|
80
|
+
|
|
81
|
+
Generation conventions:
|
|
82
|
+
- Set `layout.template` to the string `"__ACTIVE_THEME__"`. The full \
|
|
83
|
+
theme object is injected automatically after generation. Do NOT generate \
|
|
84
|
+
theme content.
|
|
85
|
+
- Do NOT set `yaxis.title`. Use `layout.title.subtitle.text` for the \
|
|
86
|
+
unit or measurement label (e.g. "Million tonnes CO2 equivalent").
|
|
87
|
+
- Use `hovertemplate` with `hoverinfo: "none"` and \
|
|
88
|
+
`hoverlabel: {namelength: 0}`.
|
|
89
|
+
- Multiple traces sharing the same x-axis should use the same `xsrc` column.
|
|
90
|
+
- For multi-series charts, create one trace per series, each with its \
|
|
91
|
+
own `ysrc` column.
|
|
92
|
+
- Always fetch a relevant template with `get_plotly_template` first and \
|
|
93
|
+
follow its structure. Add complexity on top of the template as needed, \
|
|
94
|
+
but preserve its conventions.
|
|
95
|
+
- Do not ask for clarifications or additional information. If you cannot \
|
|
96
|
+
generate a valid chart, return an empty result.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
PLOTLY_GENERATOR_TASK_PROMPT = """\
|
|
100
|
+
Generate a complete visualization content based on the user's request. \
|
|
101
|
+
Include: a descriptive title, a 1-3 sentence description, relevant EEA \
|
|
102
|
+
topics, temporal coverage (years), geographic coverage, and a valid \
|
|
103
|
+
Plotly.js chart with 'data' (traces) and 'layout' (with title, axes, \
|
|
104
|
+
and template).
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class PlotlyGeneratorAgent(AgentConfiguration):
|
|
109
|
+
"""Chart generator agent for the EEA website."""
|
|
110
|
+
|
|
111
|
+
system_prompt = PLOTLY_GENERATOR_SYSTEM_PROMPT
|
|
112
|
+
task_prompt = PLOTLY_GENERATOR_TASK_PROMPT
|
|
113
|
+
skills = ["plotly_knowledge"]
|
|
114
|
+
tools = ["get_plotly_template"]
|
|
115
|
+
output_type = "eea.plotly.models.ChartGenerationResult"
|
|
116
|
+
max_iterations = 10
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<configure xmlns="http://namespaces.zope.org/zope" xmlns:i18n="http://namespaces.zope.org/i18n"
|
|
2
|
+
xmlns:plone="http://namespaces.plone.org/plone"
|
|
3
|
+
xmlns:genai="http://namespaces.eea.europa.eu/genai"
|
|
4
|
+
i18n_domain="eea">
|
|
5
|
+
|
|
6
|
+
<i18n:registerTranslations directory="locales" />
|
|
7
|
+
|
|
8
|
+
<include file="profiles.zcml" />
|
|
9
|
+
<include file="permissions.zcml" />
|
|
10
|
+
|
|
11
|
+
<include package="eea.genai.core" file="meta.zcml" />
|
|
12
|
+
|
|
13
|
+
<include package=".browser" />
|
|
14
|
+
<include package=".restapi" />
|
|
15
|
+
<include package=".serializer" />
|
|
16
|
+
<include package=".deserializer" />
|
|
17
|
+
<include package=".upgrades" />
|
|
18
|
+
|
|
19
|
+
<interface interface="eea.plotly.interfaces.IPlotlyVisualization" />
|
|
20
|
+
|
|
21
|
+
<adapter
|
|
22
|
+
factory=".controlpanel.PlotlyControlpanel"
|
|
23
|
+
name="plotly"
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
<plone:behavior
|
|
27
|
+
name="eea.plotly.visualization"
|
|
28
|
+
title="EEA-Viz_Plotly"
|
|
29
|
+
description="Allows adding an interactive plotly chart."
|
|
30
|
+
provides="eea.plotly.behaviors.IPlotlyVisualization"
|
|
31
|
+
/>
|
|
32
|
+
|
|
33
|
+
<!-- GenAI: Context provider for chart interpretation -->
|
|
34
|
+
<genai:agentContextProvider
|
|
35
|
+
name="plotly_visualization"
|
|
36
|
+
class=".context_providers.PlotlyVisualizationProvider"
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<!-- GenAI: Plotly knowledge skill for chart generation -->
|
|
40
|
+
<genai:agentSkill
|
|
41
|
+
name="plotly_knowledge"
|
|
42
|
+
class=".skills.PlotlyKnowledgeSkill"
|
|
43
|
+
/>
|
|
44
|
+
|
|
45
|
+
<!-- GenAI: Tool to fetch chart templates by label -->
|
|
46
|
+
<genai:agentTool
|
|
47
|
+
name="get_plotly_template"
|
|
48
|
+
class=".tools.GetPlotlyTemplateTool"
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
<!-- GenAI: Content-type-specific summarizer for visualization -->
|
|
52
|
+
<genai:agent
|
|
53
|
+
name="summarizer:visualization"
|
|
54
|
+
class=".agents.PlotlySummarizerAgent"
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
<!-- GenAI: Chart generator agent -->
|
|
58
|
+
<genai:agent
|
|
59
|
+
name="plotly_generator"
|
|
60
|
+
class=".agents.PlotlyGeneratorAgent"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
</configure>
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Agent context providers for eea.plotly."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from eea.genai.core.interfaces import Enricher
|
|
7
|
+
from eea.genai.core.utils import Source
|
|
8
|
+
from eea.plotly.prompts import clean_layout
|
|
9
|
+
from eea.plotly.utils import sanitizeVisualization
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("eea.plotly")
|
|
12
|
+
|
|
13
|
+
# Arrays with more values than this threshold get replaced with
|
|
14
|
+
# statistical summaries to keep the prompt within token budget.
|
|
15
|
+
_ARRAY_TRUNCATION_THRESHOLD = 200
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PlotlyVisualizationProvider(Enricher):
|
|
19
|
+
"""Extracts Plotly chart data and adds it to the user prompt.
|
|
20
|
+
|
|
21
|
+
Reads deps.context.visualization, cleans cosmetic layout keys,
|
|
22
|
+
and truncates large data arrays to statistical summaries while
|
|
23
|
+
preserving the full Plotly JSON structure.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
name = "plotly_visualization"
|
|
27
|
+
description = "Adds Plotly chart structure and data to the user prompt"
|
|
28
|
+
|
|
29
|
+
def user_prompt(self, deps):
|
|
30
|
+
context = getattr(deps, "context", None)
|
|
31
|
+
properties = getattr(deps, "properties", None) or {}
|
|
32
|
+
|
|
33
|
+
if context is None:
|
|
34
|
+
return ""
|
|
35
|
+
|
|
36
|
+
source = Source(context, properties)
|
|
37
|
+
|
|
38
|
+
viz = getattr(source, "visualization", None)
|
|
39
|
+
if not viz or not isinstance(viz, dict):
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
prepared = prepare_visualization(viz)
|
|
43
|
+
if not prepared:
|
|
44
|
+
return ""
|
|
45
|
+
|
|
46
|
+
viz_json = json.dumps(prepared, indent=2, default=str)
|
|
47
|
+
return f"### Plotly visualization\n\n```json\n{viz_json}\n```"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def prepare_visualization(viz):
|
|
51
|
+
"""Return a cleaned copy of the visualization JSON.
|
|
52
|
+
|
|
53
|
+
1. Normalizes via sanitizeVisualization() for backward compat.
|
|
54
|
+
2. Cleans cosmetic layout keys via clean_layout().
|
|
55
|
+
3. Truncates large data arrays (>200 values) to statistical summaries.
|
|
56
|
+
"""
|
|
57
|
+
normalized = sanitizeVisualization(viz)
|
|
58
|
+
data = normalized.get("data", [])
|
|
59
|
+
layout = normalized.get("layout", {})
|
|
60
|
+
data_sources = normalized.get("dataSources", {})
|
|
61
|
+
|
|
62
|
+
if not data and not data_sources:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
result = {}
|
|
66
|
+
|
|
67
|
+
# Clean layout
|
|
68
|
+
if layout:
|
|
69
|
+
result["layout"] = clean_layout(layout)
|
|
70
|
+
|
|
71
|
+
# Process traces
|
|
72
|
+
if data:
|
|
73
|
+
result["data"] = [_truncate_trace(trace) for trace in data]
|
|
74
|
+
|
|
75
|
+
# Process dataSources
|
|
76
|
+
if data_sources:
|
|
77
|
+
result["dataSources"] = _truncate_data_sources(data_sources)
|
|
78
|
+
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _is_numeric_list(values):
|
|
83
|
+
"""True if every element is a real number (ints/floats, not bool)."""
|
|
84
|
+
if not values:
|
|
85
|
+
return False
|
|
86
|
+
return all(isinstance(v, (int, float)) and not isinstance(v, bool) for v in values)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _looks_like_year_strings(values):
|
|
90
|
+
"""True if strings parse as 4-digit calendar years.
|
|
91
|
+
|
|
92
|
+
String year arrays (e.g. ["2012","2013",...]) are otherwise treated as
|
|
93
|
+
categorical and would leak first/last samples through ``_summarize_array``.
|
|
94
|
+
"""
|
|
95
|
+
if not values:
|
|
96
|
+
return False
|
|
97
|
+
for v in values:
|
|
98
|
+
if not isinstance(v, str) or len(v) != 4 or not v.isdigit():
|
|
99
|
+
return False
|
|
100
|
+
n = int(v)
|
|
101
|
+
if not (1800 <= n <= 2200):
|
|
102
|
+
return False
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _truncate_trace(trace):
|
|
107
|
+
"""Return a copy of a trace with sensitive/large arrays summarized.
|
|
108
|
+
|
|
109
|
+
Numeric arrays are always summarized (regardless of length) so raw
|
|
110
|
+
values cannot reach the LLM. Year-like string arrays are also always
|
|
111
|
+
summarized. Other lists pass through unless they exceed the truncation
|
|
112
|
+
threshold.
|
|
113
|
+
"""
|
|
114
|
+
result = {}
|
|
115
|
+
for key, value in trace.items():
|
|
116
|
+
if isinstance(value, list) and (
|
|
117
|
+
_is_numeric_list(value)
|
|
118
|
+
or _looks_like_year_strings(value)
|
|
119
|
+
or len(value) > _ARRAY_TRUNCATION_THRESHOLD
|
|
120
|
+
):
|
|
121
|
+
result[key] = _summarize_array(value)
|
|
122
|
+
else:
|
|
123
|
+
result[key] = value
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _truncate_data_sources(data_sources):
|
|
128
|
+
"""Return a copy of dataSources with sensitive/large columns summarized."""
|
|
129
|
+
result = {}
|
|
130
|
+
for col_name, values in data_sources.items():
|
|
131
|
+
if isinstance(values, list) and (
|
|
132
|
+
_is_numeric_list(values)
|
|
133
|
+
or _looks_like_year_strings(values)
|
|
134
|
+
or len(values) > _ARRAY_TRUNCATION_THRESHOLD
|
|
135
|
+
):
|
|
136
|
+
result[col_name] = _summarize_array(values)
|
|
137
|
+
else:
|
|
138
|
+
result[col_name] = values
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _summarize_array(values):
|
|
143
|
+
"""Summarize an array into a compact description string.
|
|
144
|
+
|
|
145
|
+
Numeric arrays → ``"[N numeric values]"``. Year-like string arrays →
|
|
146
|
+
``"[N year-like values]"``. Other arrays keep first/last samples and
|
|
147
|
+
unique count because labels like country codes are useful qualitative
|
|
148
|
+
keywords for retrieval.
|
|
149
|
+
|
|
150
|
+
Quantitative details (min/max/mean) are deliberately omitted: agents
|
|
151
|
+
that consume this output are instructed to avoid numeric claims, and
|
|
152
|
+
summary text cannot leak what is not present.
|
|
153
|
+
"""
|
|
154
|
+
n = len(values)
|
|
155
|
+
|
|
156
|
+
if _is_numeric_list(values):
|
|
157
|
+
return f"[{n} numeric values]"
|
|
158
|
+
if _looks_like_year_strings(values):
|
|
159
|
+
return f"[{n} year-like values]"
|
|
160
|
+
|
|
161
|
+
first = values[:3]
|
|
162
|
+
last = values[-3:]
|
|
163
|
+
try:
|
|
164
|
+
unique_count = len(set(str(v) for v in values))
|
|
165
|
+
except Exception:
|
|
166
|
+
unique_count = "?"
|
|
167
|
+
|
|
168
|
+
return f"[{n} values, {unique_count} unique, first: {first}, last: {last}]"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Chart generation using agent system."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from plone import api
|
|
6
|
+
|
|
7
|
+
from eea.genai.core.agent import AgentDeps
|
|
8
|
+
from eea.genai.core.utils import get_executor
|
|
9
|
+
from eea.plotly.controlpanel import IPlotlySettings
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("eea.plotly")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def generate_chart(prompt, data_sources=None, context=None, request=None):
|
|
15
|
+
"""Generate a full visualization content from a natural language description."""
|
|
16
|
+
deps = AgentDeps(
|
|
17
|
+
context=context, request=request, data_sources=data_sources or None
|
|
18
|
+
)
|
|
19
|
+
result = get_executor().run_with_agent(
|
|
20
|
+
"plotly_generator", user_prompt=prompt, deps=deps
|
|
21
|
+
)
|
|
22
|
+
_inject_theme(result)
|
|
23
|
+
return result.model_dump()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _inject_theme(result):
|
|
27
|
+
"""Replace __ACTIVE_THEME__ placeholder with the actual theme object."""
|
|
28
|
+
layout = result.visualization.layout
|
|
29
|
+
if layout.get("template") != "__ACTIVE_THEME__":
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
theme = _get_active_theme()
|
|
33
|
+
if theme is None:
|
|
34
|
+
logger.warning("No active theme found; removing template placeholder")
|
|
35
|
+
layout.pop("template", None)
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
result.visualization.layout["template"] = theme
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _get_active_theme():
|
|
42
|
+
"""Return the first non-hidden theme from the Plotly control panel."""
|
|
43
|
+
try:
|
|
44
|
+
themes = api.portal.get_registry_record(
|
|
45
|
+
"themes", interface=IPlotlySettings, default=[]
|
|
46
|
+
)
|
|
47
|
+
except Exception:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
for theme in themes:
|
|
51
|
+
if not theme.get("hidden"):
|
|
52
|
+
return theme
|
|
53
|
+
return None
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Pydantic models for structured LLM output of chart operations."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class VisualizationData(BaseModel):
|
|
7
|
+
"""The Plotly chart configuration stored in the visualization field."""
|
|
8
|
+
|
|
9
|
+
data: list[dict] = Field(
|
|
10
|
+
description=(
|
|
11
|
+
"Ordered list of Plotly trace objects. Each trace must have a "
|
|
12
|
+
"'type' field, '*src' references to dataSources columns, "
|
|
13
|
+
"dereferenced inline data arrays, and 'meta.columnNames' mapping."
|
|
14
|
+
),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
layout: dict = Field(
|
|
18
|
+
description=(
|
|
19
|
+
"Plotly layout object with title, axis definitions, and "
|
|
20
|
+
"layout.template set to the active site theme."
|
|
21
|
+
),
|
|
22
|
+
default_factory=dict,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
dataSources: dict = Field(
|
|
26
|
+
description=(
|
|
27
|
+
"Dict of column_name -> array of values. Contains all data "
|
|
28
|
+
"columns referenced by traces via *src fields."
|
|
29
|
+
),
|
|
30
|
+
default_factory=dict,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
columns: list[dict] = Field(
|
|
34
|
+
description=(
|
|
35
|
+
"List of column metadata objects, each with a 'key' field "
|
|
36
|
+
"matching a dataSources column name. "
|
|
37
|
+
'Example: [{"key": "Country"}, {"key": "Value"}]'
|
|
38
|
+
),
|
|
39
|
+
default_factory=list,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ChartGenerationResult(BaseModel):
|
|
44
|
+
"""Result of LLM visualization content generation.
|
|
45
|
+
|
|
46
|
+
Represents a full visualization content type with metadata and chart data.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
title: str = Field(
|
|
50
|
+
description="Content title for the visualization page.",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
description: str = Field(
|
|
54
|
+
description=(
|
|
55
|
+
"Short summary describing the visualization (1-3 sentences). "
|
|
56
|
+
"Used as the page description / Dublin Core description."
|
|
57
|
+
),
|
|
58
|
+
default="",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
visualization: VisualizationData = Field(
|
|
62
|
+
description=(
|
|
63
|
+
"The complete Plotly visualization with traces (data), layout, "
|
|
64
|
+
"dataSources (raw dataset columns), and columns metadata."
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
topics: list[str] = Field(
|
|
69
|
+
description=(
|
|
70
|
+
"List of EEA topic tags relevant to this visualization "
|
|
71
|
+
"(e.g. 'Climate change mitigation', 'Air pollution')."
|
|
72
|
+
),
|
|
73
|
+
default_factory=list,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
temporal_coverage: list[int] = Field(
|
|
77
|
+
description=(
|
|
78
|
+
"List of years covered by the data (e.g. [2018, 2019, 2020, 2021, 2022])."
|
|
79
|
+
),
|
|
80
|
+
default_factory=list,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
geo_coverage: list[str] = Field(
|
|
84
|
+
description=(
|
|
85
|
+
"List of geographic areas covered by the data "
|
|
86
|
+
"(e.g. ['Europe', 'EU-27', 'Germany'])."
|
|
87
|
+
),
|
|
88
|
+
default_factory=list,
|
|
89
|
+
)
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
<property name="klass">collective.folderishtypes.dx.content.FolderishDocument</property>
|
|
21
21
|
<property name="behaviors">
|
|
22
22
|
<element value="eea.plotly.visualization" />
|
|
23
|
+
<element value="eea.genai.summary" />
|
|
23
24
|
<element value="eea.coremetadata.behavior" />
|
|
24
25
|
<element value="eea.dataconnector.figure_note" />
|
|
25
26
|
<element value="plone.namefromtitle" />
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Plotly prompts - reference for agent configuration.
|
|
2
|
+
|
|
3
|
+
These prompts are kept for reference when configuring agents in the control panel.
|
|
4
|
+
Copy the relevant parts into your agent's system_prompt.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
PLOTLY_SYSTEM_PROMPT = """\
|
|
8
|
+
You are a data visualization expert. You will receive a JSON object
|
|
9
|
+
containing a Plotly.js chart and metadata. The JSON follows the Plotly.js
|
|
10
|
+
specification: `data` contains trace objects (type, x, y, name, etc.),
|
|
11
|
+
`layout` contains axis definitions and titles, and `dataSources` contains
|
|
12
|
+
the raw dataset columns.
|
|
13
|
+
Produce a 3-8 sentence plain prose description that combines accessibility
|
|
14
|
+
information (chart type, axes, data ranges) with analytical insights
|
|
15
|
+
(trends, outliers, key takeaways). Do not use bullet points or markdown.
|
|
16
|
+
Write in a clear, informative style suitable for screen readers and
|
|
17
|
+
general audiences."""
|
|
18
|
+
|
|
19
|
+
# Layout keys that are purely cosmetic and don't help the LLM understand the chart.
|
|
20
|
+
# Semantically meaningful keys (barmode, showlegend, shapes, etc.) are kept.
|
|
21
|
+
IRRELEVANT_LAYOUT_KEYS = {
|
|
22
|
+
"paper_bgcolor",
|
|
23
|
+
"plot_bgcolor",
|
|
24
|
+
"font",
|
|
25
|
+
"margin",
|
|
26
|
+
"autosize",
|
|
27
|
+
"hovermode",
|
|
28
|
+
"hoverlabel",
|
|
29
|
+
"dragmode",
|
|
30
|
+
"separators",
|
|
31
|
+
"hidesources",
|
|
32
|
+
"legend",
|
|
33
|
+
"colorway",
|
|
34
|
+
"template",
|
|
35
|
+
"modebar",
|
|
36
|
+
"transition",
|
|
37
|
+
"clickmode",
|
|
38
|
+
"selectdirection",
|
|
39
|
+
"newshape",
|
|
40
|
+
"activeshape",
|
|
41
|
+
"uniformtext",
|
|
42
|
+
"waterfallgap",
|
|
43
|
+
"waterfallgroupgap",
|
|
44
|
+
"piecolorway",
|
|
45
|
+
"extendpiecolors",
|
|
46
|
+
"sunburstcolorway",
|
|
47
|
+
"extendsunburstcolors",
|
|
48
|
+
"treemapcolorway",
|
|
49
|
+
"extendtreemapcolors",
|
|
50
|
+
"iciclecolorway",
|
|
51
|
+
"extendiciclecolors",
|
|
52
|
+
"funnelgap",
|
|
53
|
+
"funnelgroupgap",
|
|
54
|
+
"bargap",
|
|
55
|
+
"bargroupgap",
|
|
56
|
+
"boxgap",
|
|
57
|
+
"boxgroupgap",
|
|
58
|
+
"violingap",
|
|
59
|
+
"violingroupgap",
|
|
60
|
+
"width",
|
|
61
|
+
"height",
|
|
62
|
+
"images",
|
|
63
|
+
"updatemenus",
|
|
64
|
+
"sliders",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Per-axis keys that leak quantitative information (year ranges, tick
|
|
69
|
+
# values, numeric formats). Axis identity (title, type, categoryorder) is
|
|
70
|
+
# qualitative and stays.
|
|
71
|
+
AXIS_LEAKY_KEYS = {
|
|
72
|
+
"range",
|
|
73
|
+
"autorange",
|
|
74
|
+
"tickvals",
|
|
75
|
+
"ticktext",
|
|
76
|
+
"tick0",
|
|
77
|
+
"dtick",
|
|
78
|
+
"tickformat",
|
|
79
|
+
"tickformatstops",
|
|
80
|
+
"rangeslider",
|
|
81
|
+
"rangeselector",
|
|
82
|
+
"rangebreaks",
|
|
83
|
+
"tickmode",
|
|
84
|
+
"nticks",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def clean_layout(layout):
|
|
89
|
+
"""Remove irrelevant cosmetic keys and leaky axis keys from layout.
|
|
90
|
+
|
|
91
|
+
Drops cosmetic top-level keys (see ``IRRELEVANT_LAYOUT_KEYS``) and then,
|
|
92
|
+
for any ``xaxis*`` / ``yaxis*`` sub-dict, strips quantitative axis keys
|
|
93
|
+
(``range``, ``tickvals``, ``tick0``, etc. — see ``AXIS_LEAKY_KEYS``)
|
|
94
|
+
so prompts cannot leak year ranges or numeric tick values to agents
|
|
95
|
+
that must not produce quantitative claims.
|
|
96
|
+
"""
|
|
97
|
+
cleaned = {k: v for k, v in layout.items() if k not in IRRELEVANT_LAYOUT_KEYS}
|
|
98
|
+
for key, value in list(cleaned.items()):
|
|
99
|
+
if isinstance(value, dict) and (
|
|
100
|
+
key.startswith("xaxis") or key.startswith("yaxis")
|
|
101
|
+
):
|
|
102
|
+
cleaned[key] = {k: v for k, v in value.items() if k not in AXIS_LEAKY_KEYS}
|
|
103
|
+
return cleaned
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<configure xmlns="http://namespaces.zope.org/zope"
|
|
2
|
+
xmlns:plone="http://namespaces.plone.org/plone"
|
|
3
|
+
i18n_domain="eea">
|
|
4
|
+
|
|
5
|
+
<plone:service
|
|
6
|
+
method="POST"
|
|
7
|
+
for="Products.CMFCore.interfaces.IContentish"
|
|
8
|
+
factory=".post.LLMGenerateChartPost"
|
|
9
|
+
name="@llm-generate-chart"
|
|
10
|
+
permission="cmf.ModifyPortalContent"
|
|
11
|
+
/>
|
|
12
|
+
|
|
13
|
+
</configure>
|