gooddata-eval 1.78.1.dev1__tar.gz → 1.78.1.dev2__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 (156) hide show
  1. gooddata_eval-1.78.1.dev1/README.md → gooddata_eval-1.78.1.dev2/PKG-INFO +86 -0
  2. gooddata_eval-1.78.1.dev1/PKG-INFO → gooddata_eval-1.78.1.dev2/README.md +57 -29
  3. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/pyproject.toml +2 -2
  4. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/cli/agentic_runner.py +4 -2
  5. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/dashboard_skill.py +80 -1
  6. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/visualization.py +132 -22
  7. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/chat/sse_client.py +4 -3
  8. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/from_insights.py +10 -6
  9. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/visualization.py +296 -6
  10. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/models.py +20 -4
  11. gooddata_eval-1.78.1.dev2/src/gooddata_eval/core/number_format.py +179 -0
  12. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/scoring.py +148 -8
  13. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_dashboard_skill.py +113 -0
  14. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_runner.py +47 -5
  15. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_visualization.py +272 -0
  16. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_from_insights.py +24 -0
  17. gooddata_eval-1.78.1.dev2/tests/test_number_format.py +195 -0
  18. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_sse_client.py +17 -0
  19. gooddata_eval-1.78.1.dev2/tests/test_visualization_evaluator.py +765 -0
  20. gooddata_eval-1.78.1.dev1/tests/test_visualization_evaluator.py +0 -369
  21. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/.gitignore +0 -0
  22. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/AGENTS.md +0 -0
  23. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/CLAUDE.md +0 -0
  24. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/LICENSE.txt +0 -0
  25. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/Makefile +0 -0
  26. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/scripts/calibrate_takeaway_judge.py +0 -0
  27. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/scripts/verify_guardrail_refusal_criteria.py +0 -0
  28. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/__init__.py +0 -0
  29. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/_version.py +0 -0
  30. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/cli/__init__.py +0 -0
  31. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/cli/main.py +0 -0
  32. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/__init__.py +0 -0
  33. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/_output.py +0 -0
  34. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/_retry.py +0 -0
  35. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/__init__.py +0 -0
  36. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_catalog.py +0 -0
  37. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_conversation_context.py +0 -0
  38. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_failed_runs.py +0 -0
  39. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_gate.py +0 -0
  40. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_langfuse.py +0 -0
  41. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/_trace_linker.py +0 -0
  42. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/alert_skill.py +0 -0
  43. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/anomaly_detection.py +0 -0
  44. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/conversation.py +0 -0
  45. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/document_skill.py +0 -0
  46. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/general_question.py +0 -0
  47. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/guardrail.py +0 -0
  48. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/kda_skill.py +0 -0
  49. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/metric_skill.py +0 -0
  50. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/report_skill.py +0 -0
  51. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/search_tool.py +0 -0
  52. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/agentic/what_if.py +0 -0
  53. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/chat/__init__.py +0 -0
  54. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/chat/render.py +0 -0
  55. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/config.py +0 -0
  56. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/connection.py +0 -0
  57. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/__init__.py +0 -0
  58. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/langfuse_source.py +0 -0
  59. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/dataset/local.py +0 -0
  60. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/__init__.py +0 -0
  61. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_deep_subset.py +0 -0
  62. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_guardrail_criteria.py +0 -0
  63. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_llm_judge.py +0 -0
  64. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_maql.py +0 -0
  65. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_rubric.py +0 -0
  66. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/_text_utils.py +0 -0
  67. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/alert_skill.py +0 -0
  68. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/base.py +0 -0
  69. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/general_question.py +0 -0
  70. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/guardrail.py +0 -0
  71. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/metric_skill.py +0 -0
  72. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/search_tool.py +0 -0
  73. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/summary.py +0 -0
  74. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/evaluators/takeaway.py +0 -0
  75. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/granularity.py +0 -0
  76. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/interaction.py +0 -0
  77. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/__init__.py +0 -0
  78. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/_env.py +0 -0
  79. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/client.py +0 -0
  80. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/experiment.py +0 -0
  81. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/item_scope.py +0 -0
  82. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/observations.py +0 -0
  83. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/otlp.py +0 -0
  84. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/langfuse/sink.py +0 -0
  85. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/__init__.py +0 -0
  86. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/console.py +0 -0
  87. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/html_report.py +0 -0
  88. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/json_report.py +0 -0
  89. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/reporting/report_template.html +0 -0
  90. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/runner.py +0 -0
  91. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/summary/__init__.py +0 -0
  92. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/summary/http_client.py +0 -0
  93. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/takeaway/__init__.py +0 -0
  94. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/takeaway/evaluate.py +0 -0
  95. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/takeaway/http_client.py +0 -0
  96. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/timing.py +0 -0
  97. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/src/gooddata_eval/core/workspace.py +0 -0
  98. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/__init__.py +0 -0
  99. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/_fake_langfuse.py +0 -0
  100. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/conftest.py +0 -0
  101. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/fixtures/sample_dataset/metric_skill_create.json +0 -0
  102. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/fixtures/sample_dataset/visualization_revenue.json +0 -0
  103. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/fixtures/sse_visualization_stream.txt +0 -0
  104. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/fixtures/takeaway_calibration.json +0 -0
  105. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_alert_skill.py +0 -0
  106. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_anomaly_detection.py +0 -0
  107. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_conversation.py +0 -0
  108. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_document_skill.py +0 -0
  109. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_gate.py +0 -0
  110. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_general_question.py +0 -0
  111. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_guardrail.py +0 -0
  112. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_join_trace.py +0 -0
  113. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_kda_skill.py +0 -0
  114. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_langfuse_trace.py +0 -0
  115. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_metric_skill.py +0 -0
  116. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_observe_experiment.py +0 -0
  117. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_run_context.py +0 -0
  118. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_search_tool.py +0 -0
  119. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_agentic_what_if.py +0 -0
  120. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_alert_skill_evaluator.py +0 -0
  121. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_chat_render.py +0 -0
  122. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_cli.py +0 -0
  123. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_connection.py +0 -0
  124. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_deep_subset.py +0 -0
  125. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_fake_langfuse.py +0 -0
  126. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_guardrail_criteria.py +0 -0
  127. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_html_report.py +0 -0
  128. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_interaction.py +0 -0
  129. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_client.py +0 -0
  130. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_e2e_fake_server.py +0 -0
  131. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_env.py +0 -0
  132. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_experiment.py +0 -0
  133. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_item_scope.py +0 -0
  134. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_observations.py +0 -0
  135. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_otlp.py +0 -0
  136. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_sink.py +0 -0
  137. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_langfuse_source.py +0 -0
  138. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_llm_judge.py +0 -0
  139. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_local_loader.py +0 -0
  140. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_maql_normalize.py +0 -0
  141. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_metric_skill_evaluator.py +0 -0
  142. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_models.py +0 -0
  143. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_reporting.py +0 -0
  144. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_runner.py +0 -0
  145. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_scoring.py +0 -0
  146. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_search_tool_evaluator.py +0 -0
  147. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_summary_client.py +0 -0
  148. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_summary_evaluator.py +0 -0
  149. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_takeaway_client.py +0 -0
  150. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_takeaway_evaluate.py +0 -0
  151. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_takeaway_evaluator.py +0 -0
  152. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_text_evaluators.py +0 -0
  153. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_timing.py +0 -0
  154. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_trace_linker.py +0 -0
  155. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tests/test_workspace.py +0 -0
  156. {gooddata_eval-1.78.1.dev1 → gooddata_eval-1.78.1.dev2}/tox.ini +0 -0
@@ -1,3 +1,32 @@
1
+ Metadata-Version: 2.5
2
+ Name: gooddata-eval
3
+ Version: 1.78.1.dev2
4
+ Summary: Evaluate the GoodData AI agent against your own questions and models.
5
+ Project-URL: Source, https://github.com/gooddata/gooddata-python-sdk
6
+ Author-email: GoodData <support@gooddata.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE.txt
9
+ Keywords: ai,analytics,cli,evaluation,gooddata,llm
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: gooddata-sdk~=1.78.1.dev2
21
+ Requires-Dist: httpx<1.0,>=0.27
22
+ Requires-Dist: jsonpatch<2.0,>=1.33
23
+ Requires-Dist: orjson<4.0.0,>=3.9.15
24
+ Requires-Dist: pydantic<3.0,>=2.6
25
+ Requires-Dist: rich<15.0,>=13.0
26
+ Provides-Extra: llm-judge
27
+ Requires-Dist: openai<2.0,>=1.45; extra == 'llm-judge'
28
+ Description-Content-Type: text/markdown
29
+
1
30
  # gooddata-eval
2
31
 
3
32
  CLI to evaluate the GoodData AI agent against a dataset of natural-language
@@ -709,6 +738,63 @@ A dataset is a folder of `.json` files, one per question:
709
738
  Supported `test_kind` values: `visualization`, `metric_skill`, `alert_skill`,
710
739
  `search_tool`, `general_question`, `guardrail`, `dashboard_summary`, `document_takeaway`.
711
740
 
741
+ ### `visualization` items
742
+
743
+ The `expected_output` lists the candidate charts in the AAC format, and the item passes when
744
+ the agent's chart matches one of them. Keys beside the candidates add item-level checks:
745
+
746
+ ```json
747
+ {
748
+ "id": "sankey-001",
749
+ "dataset_name": "agent_visualization_extended_charts",
750
+ "test_kind": "visualization",
751
+ "question": "Show how net sales flow from product category to customer country.",
752
+ "expected_output": {
753
+ "visualization": {
754
+ "type": "sankey_chart",
755
+ "query": {"fields": {"m": {"using": "metric/net_sales"}, "c": {"using": "label/product_category"},
756
+ "d": {"using": "label/customer_country"}}},
757
+ "metrics": ["m"],
758
+ "from": "c",
759
+ "to": "d",
760
+ "buckets_strict": true
761
+ },
762
+ "requires_visualization_part": true
763
+ }
764
+ }
765
+ ```
766
+
767
+ Item-level keys, all optional except the candidates:
768
+
769
+ - `visualization` — one candidate chart, or a list of them. `expected_outputs`
770
+ (`[{"visualization": {...}}, ...]`) is the same list in the agentic fixture form.
771
+ - `requires_execution` — the agent must also run the chart it built.
772
+ - `expects_visualization: false` — the agent must answer without a chart, for example by asking
773
+ for a part the request leaves out. The item then lists no candidates.
774
+ - `visualization_optional: true` — either an answer without a chart, or a chart that matches a
775
+ candidate, is right.
776
+ - `answer_must_include` / `answer_must_not_match` — regular expressions, matched without regard
777
+ to case against the final answer. Every `answer_must_include` pattern must match, and no
778
+ `answer_must_not_match` pattern may.
779
+ - `format_intents` — per metric uri, how the metric's numbers must read, for example
780
+ `{"metric/net_sales": {"divide_by": 1000000, "decimals": 1, "suffix": "M"}}`.
781
+ - `requires_visualization_part` — the chart must come back as a part of the answer, not only as
782
+ the arguments of the chart tool call.
783
+
784
+ Switches on a candidate chart:
785
+
786
+ - `buckets_strict` — compare bucket by bucket, not only the union of the buckets, so swapped
787
+ heatmap rows and columns or a reversed sankey fail.
788
+ - `metrics_ordered` — compare the metrics by position, for bullet roles and bubble axes.
789
+
790
+ Any other key, at item level or on a candidate chart, makes the item error before any request
791
+ is sent, because a misspelled key would otherwise be dropped and its check would never run:
792
+
793
+ ```
794
+ ValueError: the expectation states ['answer_must_inlcude'], which is not one of ['answer_must_include', ...]
795
+ ValueError: a candidate chart states ['bucket_strict'], which the chart model does not read
796
+ ```
797
+
712
798
  ### `dashboard_summary` items
713
799
 
714
800
  Summary items call the dedicated summary endpoint
@@ -1,32 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: gooddata-eval
3
- Version: 1.78.1.dev1
4
- Summary: Evaluate the GoodData AI agent against your own questions and models.
5
- Project-URL: Source, https://github.com/gooddata/gooddata-python-sdk
6
- Author-email: GoodData <support@gooddata.com>
7
- License-Expression: MIT
8
- License-File: LICENSE.txt
9
- Keywords: ai,analytics,cli,evaluation,gooddata,llm
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Environment :: Console
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Topic :: Scientific/Engineering
17
- Classifier: Topic :: Software Development
18
- Classifier: Typing :: Typed
19
- Requires-Python: >=3.10
20
- Requires-Dist: gooddata-sdk~=1.78.1.dev1
21
- Requires-Dist: httpx<1.0,>=0.27
22
- Requires-Dist: jsonpatch<2.0,>=1.33
23
- Requires-Dist: orjson<4.0.0,>=3.9.15
24
- Requires-Dist: pydantic<3.0,>=2.6
25
- Requires-Dist: rich<15.0,>=13.0
26
- Provides-Extra: llm-judge
27
- Requires-Dist: openai<2.0,>=1.45; extra == 'llm-judge'
28
- Description-Content-Type: text/markdown
29
-
30
1
  # gooddata-eval
31
2
 
32
3
  CLI to evaluate the GoodData AI agent against a dataset of natural-language
@@ -738,6 +709,63 @@ A dataset is a folder of `.json` files, one per question:
738
709
  Supported `test_kind` values: `visualization`, `metric_skill`, `alert_skill`,
739
710
  `search_tool`, `general_question`, `guardrail`, `dashboard_summary`, `document_takeaway`.
740
711
 
712
+ ### `visualization` items
713
+
714
+ The `expected_output` lists the candidate charts in the AAC format, and the item passes when
715
+ the agent's chart matches one of them. Keys beside the candidates add item-level checks:
716
+
717
+ ```json
718
+ {
719
+ "id": "sankey-001",
720
+ "dataset_name": "agent_visualization_extended_charts",
721
+ "test_kind": "visualization",
722
+ "question": "Show how net sales flow from product category to customer country.",
723
+ "expected_output": {
724
+ "visualization": {
725
+ "type": "sankey_chart",
726
+ "query": {"fields": {"m": {"using": "metric/net_sales"}, "c": {"using": "label/product_category"},
727
+ "d": {"using": "label/customer_country"}}},
728
+ "metrics": ["m"],
729
+ "from": "c",
730
+ "to": "d",
731
+ "buckets_strict": true
732
+ },
733
+ "requires_visualization_part": true
734
+ }
735
+ }
736
+ ```
737
+
738
+ Item-level keys, all optional except the candidates:
739
+
740
+ - `visualization` — one candidate chart, or a list of them. `expected_outputs`
741
+ (`[{"visualization": {...}}, ...]`) is the same list in the agentic fixture form.
742
+ - `requires_execution` — the agent must also run the chart it built.
743
+ - `expects_visualization: false` — the agent must answer without a chart, for example by asking
744
+ for a part the request leaves out. The item then lists no candidates.
745
+ - `visualization_optional: true` — either an answer without a chart, or a chart that matches a
746
+ candidate, is right.
747
+ - `answer_must_include` / `answer_must_not_match` — regular expressions, matched without regard
748
+ to case against the final answer. Every `answer_must_include` pattern must match, and no
749
+ `answer_must_not_match` pattern may.
750
+ - `format_intents` — per metric uri, how the metric's numbers must read, for example
751
+ `{"metric/net_sales": {"divide_by": 1000000, "decimals": 1, "suffix": "M"}}`.
752
+ - `requires_visualization_part` — the chart must come back as a part of the answer, not only as
753
+ the arguments of the chart tool call.
754
+
755
+ Switches on a candidate chart:
756
+
757
+ - `buckets_strict` — compare bucket by bucket, not only the union of the buckets, so swapped
758
+ heatmap rows and columns or a reversed sankey fail.
759
+ - `metrics_ordered` — compare the metrics by position, for bullet roles and bubble axes.
760
+
761
+ Any other key, at item level or on a candidate chart, makes the item error before any request
762
+ is sent, because a misspelled key would otherwise be dropped and its check would never run:
763
+
764
+ ```
765
+ ValueError: the expectation states ['answer_must_inlcude'], which is not one of ['answer_must_include', ...]
766
+ ValueError: a candidate chart states ['bucket_strict'], which the chart model does not read
767
+ ```
768
+
741
769
  ### `dashboard_summary` items
742
770
 
743
771
  Summary items call the dedicated summary endpoint
@@ -1,7 +1,7 @@
1
1
  # (C) 2026 GoodData Corporation
2
2
  [project]
3
3
  name = "gooddata-eval"
4
- version = "1.78.1.dev1"
4
+ version = "1.78.1.dev2"
5
5
  description = "Evaluate the GoodData AI agent against your own questions and models."
6
6
  readme = "README.md"
7
7
  license = "MIT"
@@ -11,7 +11,7 @@ authors = [
11
11
  keywords = ["gooddata", "ai", "evaluation", "llm", "analytics", "cli"]
12
12
  requires-python = ">=3.10"
13
13
  dependencies = [
14
- "gooddata-sdk~=1.78.1.dev1",
14
+ "gooddata-sdk~=1.78.1.dev2",
15
15
  "httpx>=0.27,<1.0",
16
16
  "jsonpatch>=1.33,<2.0",
17
17
  "orjson>=3.9.15,<4.0.0",
@@ -26,7 +26,7 @@ from gooddata_eval.core.agentic.visualization import evaluate_agentic_visualizat
26
26
  from gooddata_eval.core.agentic.what_if import evaluate_agentic_what_if
27
27
  from gooddata_eval.core.config import ReasoningEffort
28
28
  from gooddata_eval.core.evaluators._llm_judge import JudgeResponseError
29
- from gooddata_eval.core.evaluators.visualization import requires_execution_of
29
+ from gooddata_eval.core.evaluators.visualization import expectation_options
30
30
  from gooddata_eval.core.models import AgenticEvalOutcome, CreatedVisualization, DatasetItem
31
31
  from gooddata_eval.core.runner import EvalReport, ItemReport
32
32
  from gooddata_eval.core.timing import TraceRecorder
@@ -119,6 +119,8 @@ def _parse_visualization_expected(expected_output: Any) -> list[CreatedVisualiza
119
119
  {"visualization": {...}} or {"visualization": [{...}]} <- single/multi candidate
120
120
  [{"visualization": {...}}, ...] <- bare list
121
121
  """
122
+ if isinstance(expected_output, dict) and expected_output.get("expects_visualization") is False:
123
+ return []
122
124
  if isinstance(expected_output, dict):
123
125
  raw_list = expected_output.get("expected_outputs")
124
126
  if raw_list is not None:
@@ -187,7 +189,7 @@ def _dispatch_agentic(
187
189
  workspace_id=workspace_id,
188
190
  question=item.question,
189
191
  expected_outputs=_parse_visualization_expected(eo),
190
- requires_execution=requires_execution_of(eo),
192
+ options=expectation_options(eo),
191
193
  k=k,
192
194
  gate=gate,
193
195
  agent_id=agent_id,
@@ -40,6 +40,7 @@ from gooddata_eval.core.models import (
40
40
  build_latency_breakdown,
41
41
  shift_and_index_events,
42
42
  )
43
+ from gooddata_eval.core.scoring import AAC_VISUALIZATION_TYPES, same_viz_type
43
44
  from gooddata_eval.core.timing import PhaseTimings, log_timer, sum_timings
44
45
 
45
46
  _DEFAULT_K = 1
@@ -582,6 +583,64 @@ def _check_date_bindings(widgets: list[dict], new_ids: set[str], expected: list[
582
583
  return failures
583
584
 
584
585
 
586
+ def _has_chart_types(expected_output: dict) -> bool:
587
+ """Whether any expected chart states the type it must be placed as."""
588
+ return any(isinstance(v, dict) and "chart_type" in v for v in expected_output.get("visualizations") or [])
589
+
590
+
591
+ def _bodies_by_id(*references: dict) -> dict[str, dict]:
592
+ """Every chart body the response's references carry, existing and authored, by chart id."""
593
+ bodies: dict[str, dict] = {}
594
+ for refs in references:
595
+ for key in ("visualizations", "new_visualizations"):
596
+ for body in refs.get(key) or []:
597
+ if isinstance(body, dict) and body.get("id"):
598
+ bodies[body["id"]] = body
599
+ return bodies
600
+
601
+
602
+ def _check_chart_types(
603
+ widgets: list[dict], new_ids: set[str], expected: list[dict], bodies: dict[str, dict]
604
+ ) -> tuple[list[str], list[str]]:
605
+ """Every widget of a chart that states a ``chart_type`` must show a chart of exactly that type.
606
+
607
+ A widget names only the chart id, so the type is read from the chart body the references
608
+ carry. The title alone cannot catch a chart authored as another type than the one asked
609
+ for, which is the silent swap GDAI-2054 rules out.
610
+
611
+ Returns the failures and the notes. A widget whose chart has no body in the references
612
+ cannot be checked: gen-ai carries a body only when it could resolve one, so a missing
613
+ body is a platform fault, noted as ``_check_references`` notes a missing id, and not
614
+ scored as the agent's.
615
+ """
616
+ failures: list[str] = []
617
+ notes: list[str] = []
618
+ for exp in expected:
619
+ if not isinstance(exp, dict) or "chart_type" not in exp:
620
+ continue
621
+ wanted = exp["chart_type"]
622
+ label = exp.get("title") if exp.get("id") is None else exp.get("id")
623
+ candidates = _matching_widgets(widgets, new_ids, exp)
624
+ if not candidates:
625
+ failures.append(f"cannot check the type of chart {label!r}: it is not on the dashboard")
626
+ continue
627
+ types: list[str] = []
628
+ for widget in candidates:
629
+ chart_type = (bodies.get(widget.get("visualization")) or {}).get("type")
630
+ if isinstance(chart_type, str):
631
+ types.append(chart_type)
632
+ else:
633
+ notes.append(
634
+ f"cannot check the type of chart {label!r}: the response's references carry no body "
635
+ f"for {widget.get('visualization')!r}"
636
+ )
637
+ # Every placement, as for date bindings: one right placement must not hide a wrong one.
638
+ if not all(same_viz_type(wanted, t) for t in types):
639
+ found = ", ".join(repr(t) for t in types)
640
+ failures.append(f"chart {label!r} is {found}, expected {wanted!r}")
641
+ return failures, notes
642
+
643
+
585
644
  def _tabs_of(dashboard: dict) -> list[dict]:
586
645
  """The dashboard's tabs, reading a document with root sections as its single tab."""
587
646
  tabs = dashboard.get("tabs")
@@ -936,6 +995,12 @@ def _validate_layout_expectations(expected_output: dict) -> None:
936
995
  for chart in expected_output.get("visualizations") or []:
937
996
  if isinstance(chart, dict) and "date_dataset" in chart and not _is_dataset_or_null(chart["date_dataset"]):
938
997
  raise ValueError(f"date_dataset must name a date dataset or be null, got {chart!r}")
998
+ if (
999
+ isinstance(chart, dict)
1000
+ and "chart_type" in chart
1001
+ and (not isinstance(chart["chart_type"], str) or chart["chart_type"] not in AAC_VISUALIZATION_TYPES)
1002
+ ):
1003
+ raise ValueError(f"chart_type must be an AAC visualization type, got {chart!r}")
939
1004
 
940
1005
  if "tabs" in expected_output:
941
1006
  tabs = expected_output["tabs"]
@@ -1037,6 +1102,7 @@ class _Applies:
1037
1102
  tab_filters: bool = False
1038
1103
  date_filters: bool = False
1039
1104
  preserved_widgets: bool = False
1105
+ chart_types: bool = False
1040
1106
 
1041
1107
  @classmethod
1042
1108
  def of(cls, expected_output: dict) -> _Applies:
@@ -1049,6 +1115,7 @@ class _Applies:
1049
1115
  tab_filters="tab_filters" in expected_output,
1050
1116
  date_filters="date_filters" in expected_output,
1051
1117
  preserved_widgets="preserved_widgets" in expected_output,
1118
+ chart_types=_has_chart_types(expected_output),
1052
1119
  )
1053
1120
 
1054
1121
 
@@ -1089,6 +1156,7 @@ class DashboardEvaluation:
1089
1156
  tab_filters_correct: bool = False
1090
1157
  date_filters_correct: bool = False
1091
1158
  widgets_preserved: bool = False
1159
+ chart_types_correct: bool = False
1092
1160
  failures: list[str] = field(default_factory=list)
1093
1161
  notes: list[str] = field(default_factory=list)
1094
1162
 
@@ -1129,6 +1197,8 @@ class DashboardEvaluation:
1129
1197
  checks["dashboard_date_filters_correct"] = self.date_filters_correct
1130
1198
  if self.applies.preserved_widgets:
1131
1199
  checks["dashboard_widgets_preserved"] = self.widgets_preserved
1200
+ if self.applies.chart_types:
1201
+ checks["dashboard_chart_types_correct"] = self.chart_types_correct
1132
1202
  return checks
1133
1203
 
1134
1204
  @property
@@ -1280,12 +1350,14 @@ def evaluate_dashboard_response(
1280
1350
  | {v.get("id") for v in patch_references.get("visualizations") or [] if v.get("id")}
1281
1351
  | new_ids
1282
1352
  )
1353
+ bodies = _bodies_by_id(base_references, patch_references)
1283
1354
  else:
1284
1355
  if actual_saved_id is not None:
1285
1356
  saved_failures.append(f"a new draft must not be saved yet, but it reports id {actual_saved_id!r}")
1286
1357
  document = base
1287
1358
  new_ids = {v.get("id") for v in base_references.get("new_visualizations") or [] if v.get("id")}
1288
1359
  known_ids = {v.get("id") for v in base_references.get("visualizations") or [] if v.get("id")} | new_ids
1360
+ bodies = _bodies_by_id(base_references)
1289
1361
 
1290
1362
  if document is None:
1291
1363
  return DashboardEvaluation(
@@ -1337,6 +1409,11 @@ def evaluate_dashboard_response(
1337
1409
  preserved_failures = (
1338
1410
  _check_preserved_widgets(widgets, expected_output["preserved_widgets"]) if applies.preserved_widgets else []
1339
1411
  )
1412
+ chart_type_failures, chart_type_notes = (
1413
+ _check_chart_types(widgets, new_ids, expected_output.get("visualizations") or [], bodies)
1414
+ if applies.chart_types
1415
+ else ([], [])
1416
+ )
1340
1417
 
1341
1418
  return DashboardEvaluation(
1342
1419
  drafted=True,
@@ -1356,6 +1433,7 @@ def evaluate_dashboard_response(
1356
1433
  tab_filters_correct=not tab_filter_failures,
1357
1434
  date_filters_correct=not date_filter_failures,
1358
1435
  widgets_preserved=not preserved_failures,
1436
+ chart_types_correct=not chart_type_failures,
1359
1437
  failures=[
1360
1438
  *chart_failures,
1361
1439
  *saved_failures,
@@ -1366,9 +1444,10 @@ def evaluate_dashboard_response(
1366
1444
  *tab_filter_failures,
1367
1445
  *date_filter_failures,
1368
1446
  *preserved_failures,
1447
+ *chart_type_failures,
1369
1448
  *new_failures,
1370
1449
  ],
1371
- notes=[*title_notes, *reference_notes],
1450
+ notes=[*title_notes, *reference_notes, *chart_type_notes],
1372
1451
  )
1373
1452
 
1374
1453