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.
Files changed (153) hide show
  1. {eea_plotly-2.3/eea.plotly.egg-info → eea_plotly-2.4}/PKG-INFO +7 -1
  2. {eea_plotly-2.3 → eea_plotly-2.4}/docs/HISTORY.txt +5 -0
  3. eea_plotly-2.4/eea/plotly/agents.py +116 -0
  4. eea_plotly-2.4/eea/plotly/configure.zcml +63 -0
  5. eea_plotly-2.4/eea/plotly/context_providers.py +168 -0
  6. eea_plotly-2.4/eea/plotly/generate.py +53 -0
  7. eea_plotly-2.4/eea/plotly/models.py +89 -0
  8. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/types/visualization.xml +1 -0
  9. eea_plotly-2.4/eea/plotly/prompts.py +103 -0
  10. eea_plotly-2.4/eea/plotly/restapi/chart/configure.zcml +13 -0
  11. eea_plotly-2.4/eea/plotly/restapi/chart/post.py +58 -0
  12. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/configure.zcml +2 -0
  13. eea_plotly-2.4/eea/plotly/restapi/controlpanel/__init__.py +0 -0
  14. eea_plotly-2.4/eea/plotly/restapi/summarize/__init__.py +1 -0
  15. eea_plotly-2.4/eea/plotly/restapi/summarize/configure.zcml +11 -0
  16. eea_plotly-2.4/eea/plotly/restapi/summarize/post.py +142 -0
  17. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/serializer/utils.py +1 -0
  18. eea_plotly-2.4/eea/plotly/skills.py +168 -0
  19. eea_plotly-2.4/eea/plotly/tests/test_leak_prevention.py +180 -0
  20. eea_plotly-2.4/eea/plotly/tools.py +95 -0
  21. eea_plotly-2.4/eea/plotly/version.txt +1 -0
  22. {eea_plotly-2.3 → eea_plotly-2.4/eea.plotly.egg-info}/PKG-INFO +7 -1
  23. {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/SOURCES.txt +14 -0
  24. {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/requires.txt +1 -0
  25. {eea_plotly-2.3 → eea_plotly-2.4}/setup.py +1 -0
  26. eea_plotly-2.3/eea/plotly/configure.zcml +0 -30
  27. eea_plotly-2.3/eea/plotly/version.txt +0 -1
  28. {eea_plotly-2.3 → eea_plotly-2.4}/CONTRIBUTING.md +0 -0
  29. {eea_plotly-2.3 → eea_plotly-2.4}/MANIFEST.in +0 -0
  30. {eea_plotly-2.3 → eea_plotly-2.4}/README.rst +0 -0
  31. {eea_plotly-2.3 → eea_plotly-2.4}/docs/LICENSE.GPL +0 -0
  32. {eea_plotly-2.3 → eea_plotly-2.4}/docs/LICENSE.txt +0 -0
  33. {eea_plotly-2.3 → eea_plotly-2.4}/eea/__init__.py +0 -0
  34. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/README.txt +0 -0
  35. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/__init__.py +0 -0
  36. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/behaviors.py +0 -0
  37. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/__init__.py +0 -0
  38. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/configure.zcml +0 -0
  39. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/controlpanel.py +0 -0
  40. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/preview.py +0 -0
  41. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/preview_adapter/adapter.py +0 -0
  42. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/preview_adapter/soer_miniature.py +0 -0
  43. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/browser/pt/visualization_view.pt +0 -0
  44. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/controlpanel.py +0 -0
  45. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/deserializer/blocks.py +0 -0
  46. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/deserializer/configure.zcml +0 -0
  47. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/deserializer/dxcontent.py +0 -0
  48. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/deserializer/dxfields.py +0 -0
  49. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/interfaces.py +0 -0
  50. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/io_csv.py +0 -0
  51. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/io_json.py +0 -0
  52. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/__init__.py +0 -0
  53. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/bg/LC_MESSAGES/eea.po +0 -0
  54. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/bg/LC_MESSAGES/plone.po +0 -0
  55. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/cs/LC_MESSAGES/eea.po +0 -0
  56. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/cs/LC_MESSAGES/plone.po +0 -0
  57. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/da/LC_MESSAGES/eea.po +0 -0
  58. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/da/LC_MESSAGES/plone.po +0 -0
  59. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/de/LC_MESSAGES/eea.po +0 -0
  60. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/de/LC_MESSAGES/plone.po +0 -0
  61. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/eea.pot +0 -0
  62. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/el/LC_MESSAGES/eea.po +0 -0
  63. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/el/LC_MESSAGES/plone.po +0 -0
  64. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/en/LC_MESSAGES/eea.po +0 -0
  65. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/en/LC_MESSAGES/plone.po +0 -0
  66. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/es/LC_MESSAGES/eea.po +0 -0
  67. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/es/LC_MESSAGES/plone.po +0 -0
  68. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/et/LC_MESSAGES/eea.po +0 -0
  69. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/et/LC_MESSAGES/plone.po +0 -0
  70. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/eu/LC_MESSAGES/eea.po +0 -0
  71. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/eu/LC_MESSAGES/plone.po +0 -0
  72. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/fi/LC_MESSAGES/eea.po +0 -0
  73. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/fi/LC_MESSAGES/plone.po +0 -0
  74. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/fr/LC_MESSAGES/eea.po +0 -0
  75. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/fr/LC_MESSAGES/plone.po +0 -0
  76. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/hr/LC_MESSAGES/eea.po +0 -0
  77. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/hr/LC_MESSAGES/plone.po +0 -0
  78. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/hu/LC_MESSAGES/eea.po +0 -0
  79. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/hu/LC_MESSAGES/plone.po +0 -0
  80. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/is/LC_MESSAGES/eea.po +0 -0
  81. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/is/LC_MESSAGES/plone.po +0 -0
  82. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/it/LC_MESSAGES/eea.po +0 -0
  83. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/it/LC_MESSAGES/plone.po +0 -0
  84. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/kl/LC_MESSAGES/eea.po +0 -0
  85. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/kl/LC_MESSAGES/plone.po +0 -0
  86. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/lt/LC_MESSAGES/eea.po +0 -0
  87. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/lt/LC_MESSAGES/plone.po +0 -0
  88. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/lv/LC_MESSAGES/eea.po +0 -0
  89. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/lv/LC_MESSAGES/plone.po +0 -0
  90. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/mt/LC_MESSAGES/eea.po +0 -0
  91. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/mt/LC_MESSAGES/plone.po +0 -0
  92. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/nl/LC_MESSAGES/eea.po +0 -0
  93. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/nl/LC_MESSAGES/plone.po +0 -0
  94. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/no/LC_MESSAGES/eea.po +0 -0
  95. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/no/LC_MESSAGES/plone.po +0 -0
  96. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pl/LC_MESSAGES/eea.po +0 -0
  97. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pl/LC_MESSAGES/plone.po +0 -0
  98. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/plone-manual.pot +0 -0
  99. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pt/LC_MESSAGES/eea.po +0 -0
  100. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pt/LC_MESSAGES/plone.po +0 -0
  101. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pt_BR/LC_MESSAGES/eea.po +0 -0
  102. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/pt_BR/LC_MESSAGES/plone.po +0 -0
  103. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/ro/LC_MESSAGES/eea.po +0 -0
  104. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/ro/LC_MESSAGES/plone.po +0 -0
  105. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/ru/LC_MESSAGES/eea.po +0 -0
  106. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/ru/LC_MESSAGES/plone.po +0 -0
  107. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sk/LC_MESSAGES/eea.po +0 -0
  108. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sk/LC_MESSAGES/plone.po +0 -0
  109. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sl/LC_MESSAGES/eea.po +0 -0
  110. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sl/LC_MESSAGES/plone.po +0 -0
  111. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sv/LC_MESSAGES/eea.po +0 -0
  112. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/sv/LC_MESSAGES/plone.po +0 -0
  113. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/tr/LC_MESSAGES/eea.po +0 -0
  114. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/tr/LC_MESSAGES/plone.po +0 -0
  115. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/update.sh +0 -0
  116. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/zh_TW/LC_MESSAGES/eea.po +0 -0
  117. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/locales/zh_TW/LC_MESSAGES/plone.po +0 -0
  118. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/permissions.zcml +0 -0
  119. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/browserlayer.xml +0 -0
  120. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/controlpanel.json +0 -0
  121. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/controlpanel.xml +0 -0
  122. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/metadata.xml +0 -0
  123. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/registry.xml +0 -0
  124. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/rolemap.xml +0 -0
  125. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/default/types.xml +0 -0
  126. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/uninstall/browserlayer.xml +0 -0
  127. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/uninstall/controlpanel.xml +0 -0
  128. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles/uninstall/registry.xml +0 -0
  129. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/profiles.zcml +0 -0
  130. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/__init__.py +0 -0
  131. {eea_plotly-2.3/eea/plotly/restapi/controlpanel → eea_plotly-2.4/eea/plotly/restapi/chart}/__init__.py +0 -0
  132. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/controlpanel/configure.zcml +0 -0
  133. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/controlpanel/get.py +0 -0
  134. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/controlpanel/reset.py +0 -0
  135. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/visualization/configure.zcml +0 -0
  136. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/restapi/visualization/get.py +0 -0
  137. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/serializer/blocks.py +0 -0
  138. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/serializer/configure.zcml +0 -0
  139. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/serializer/dxfields.py +0 -0
  140. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/setuphandlers.py +0 -0
  141. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/tests/__init__.py +0 -0
  142. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/tests/base.py +0 -0
  143. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/tests/test_doctests.py +0 -0
  144. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/tests/test_utils.py +0 -0
  145. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/upgrades/__init__.py +0 -0
  146. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/upgrades/configure.zcml +0 -0
  147. {eea_plotly-2.3 → eea_plotly-2.4}/eea/plotly/utils.py +0 -0
  148. {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/dependency_links.txt +0 -0
  149. {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/entry_points.txt +0 -0
  150. {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/namespace_packages.txt +0 -0
  151. {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/not-zip-safe +0 -0
  152. {eea_plotly-2.3 → eea_plotly-2.4}/eea.plotly.egg-info/top_level.txt +0 -0
  153. {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
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>