mammoth-cli 2.0.40__tar.gz → 2.0.41__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 (224) hide show
  1. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/PKG-INFO +1 -1
  2. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/__init__.py +1 -1
  3. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +14 -9
  4. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +9 -0
  5. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/dashboard.py +69 -1
  6. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/file.py +15 -0
  7. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/view.py +106 -0
  8. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/view_ops.py +11 -3
  9. mammoth_cli-2.0.41/mammoth_cli/services/dashboard_review.py +284 -0
  10. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/pyproject.toml +1 -1
  11. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/.gitignore +0 -0
  12. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/LICENSE +0 -0
  13. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/README.md +0 -0
  14. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/__main__.py +0 -0
  15. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/app.py +0 -0
  16. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +0 -0
  17. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
  18. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
  19. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
  20. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
  21. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
  22. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
  23. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
  24. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
  25. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
  26. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
  27. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
  28. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
  29. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
  30. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
  31. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
  32. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
  33. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
  34. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
  35. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
  36. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
  37. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
  38. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
  39. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
  40. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
  41. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
  42. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
  43. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
  44. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
  45. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
  46. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
  47. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
  48. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
  49. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
  50. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
  51. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
  52. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
  53. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
  54. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
  55. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
  56. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
  57. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
  58. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
  59. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
  60. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
  61. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
  62. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
  63. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
  64. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
  65. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
  66. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
  67. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
  68. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
  69. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
  70. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
  71. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
  72. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
  73. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
  74. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
  75. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
  76. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
  77. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
  78. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
  79. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
  80. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
  81. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
  82. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
  83. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/__init__.py +0 -0
  84. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/activity.py +0 -0
  85. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/addon.py +0 -0
  86. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/agent.py +0 -0
  87. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/ai.py +0 -0
  88. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/annotation.py +0 -0
  89. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/auth.py +0 -0
  90. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/automation.py +0 -0
  91. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/batch.py +0 -0
  92. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/billing.py +0 -0
  93. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/browse.py +0 -0
  94. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/capability.py +0 -0
  95. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/client_app.py +0 -0
  96. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/completion.py +0 -0
  97. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/config.py +0 -0
  98. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/connector.py +0 -0
  99. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/context.py +0 -0
  100. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/data_app.py +0 -0
  101. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/dataset.py +0 -0
  102. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/doctor.py +0 -0
  103. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/external_key.py +0 -0
  104. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/folder.py +0 -0
  105. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/job.py +0 -0
  106. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/notification.py +0 -0
  107. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/parameter.py +0 -0
  108. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/project.py +0 -0
  109. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/registry.py +0 -0
  110. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/report.py +0 -0
  111. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/schedule.py +0 -0
  112. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/schema.py +0 -0
  113. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/skill.py +0 -0
  114. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/snippet.py +0 -0
  115. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/support.py +0 -0
  116. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/template.py +0 -0
  117. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/trash.py +0 -0
  118. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/upgrade.py +0 -0
  119. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/user.py +0 -0
  120. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/webhook.py +0 -0
  121. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/workflow.py +0 -0
  122. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/commands/workspace.py +0 -0
  123. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/context/__init__.py +0 -0
  124. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/context/credentials.py +0 -0
  125. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/context/endpoint.py +0 -0
  126. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/context/profiles.py +0 -0
  127. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/context/resolver.py +0 -0
  128. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/contracts/__init__.py +0 -0
  129. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/contracts/auth.py +0 -0
  130. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/errors/__init__.py +0 -0
  131. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/errors/envelope.py +0 -0
  132. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/manifest/__init__.py +0 -0
  133. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/manifest/loader.py +0 -0
  134. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/messages/__init__.py +0 -0
  135. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/output/__init__.py +0 -0
  136. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/output/envelope.py +0 -0
  137. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/output/normalize.py +0 -0
  138. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/output/policy.py +0 -0
  139. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/output/progress.py +0 -0
  140. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/output/render.py +0 -0
  141. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/py.typed +0 -0
  142. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/__init__.py +0 -0
  143. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/confirm.py +0 -0
  144. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/dryrun.py +0 -0
  145. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/executor.py +0 -0
  146. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/input_loader.py +0 -0
  147. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/invocation.py +0 -0
  148. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/options.py +0 -0
  149. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/parents.py +0 -0
  150. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/runlog.py +0 -0
  151. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/session.py +0 -0
  152. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/strict.py +0 -0
  153. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/updates.py +0 -0
  154. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/runtime/validate.py +0 -0
  155. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/__init__.py +0 -0
  156. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/argspec.py +0 -0
  157. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/coerce.py +0 -0
  158. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/command_contract.py +0 -0
  159. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/conditions.py +0 -0
  160. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/dashboard_pages.py +0 -0
  161. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/data_quality.py +0 -0
  162. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/dispatch.py +0 -0
  163. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/factory.py +0 -0
  164. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/input_fields.py +0 -0
  165. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/mapping.py +0 -0
  166. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/openapi_types.py +0 -0
  167. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/positionals.py +0 -0
  168. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/protocol.py +0 -0
  169. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/sdk_service.py +0 -0
  170. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/testing.py +0 -0
  171. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/services/type_system.py +0 -0
  172. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/skills/__init__.py +0 -0
  173. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/skills/installer.py +0 -0
  174. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/skills/steering.py +0 -0
  175. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/mammoth_cli/testing.py +0 -0
  176. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/activity.yaml +0 -0
  177. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/addon.yaml +0 -0
  178. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/agent.yaml +0 -0
  179. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/ai.yaml +0 -0
  180. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/annotation.yaml +0 -0
  181. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/auth.yaml +0 -0
  182. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/automation.yaml +0 -0
  183. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/batch.yaml +0 -0
  184. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/billing.yaml +0 -0
  185. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/browse.yaml +0 -0
  186. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/capability.yaml +0 -0
  187. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/client-app.yaml +0 -0
  188. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/completion.yaml +0 -0
  189. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/config.yaml +0 -0
  190. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/connector.yaml +0 -0
  191. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/context.yaml +0 -0
  192. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/dashboard.yaml +0 -0
  193. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/data-app.yaml +0 -0
  194. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/dataset.yaml +0 -0
  195. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/doctor.yaml +0 -0
  196. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/external-key.yaml +0 -0
  197. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/file.yaml +0 -0
  198. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/folder.yaml +0 -0
  199. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/job.yaml +0 -0
  200. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/log.yaml +0 -0
  201. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/notification.yaml +0 -0
  202. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/parameter.yaml +0 -0
  203. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/project.yaml +0 -0
  204. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/report.yaml +0 -0
  205. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/schedule.yaml +0 -0
  206. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/schema.yaml +0 -0
  207. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/skill.yaml +0 -0
  208. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/snippet.yaml +0 -0
  209. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/support.yaml +0 -0
  210. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/template.yaml +0 -0
  211. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/trash.yaml +0 -0
  212. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/upgrade.yaml +0 -0
  213. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/user.yaml +0 -0
  214. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/version.yaml +0 -0
  215. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/view.yaml +0 -0
  216. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/webhook.yaml +0 -0
  217. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/workflow.yaml +0 -0
  218. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/commands/workspace.yaml +0 -0
  219. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/openapi-operations.yaml +0 -0
  220. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/schema-v1.json +0 -0
  221. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/sdk-catalog.source.yaml +0 -0
  222. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/manifests/sdk-methods.yaml +0 -0
  223. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/openapi/metadata.json +0 -0
  224. {mammoth_cli-2.0.40 → mammoth_cli-2.0.41}/spec/openapi/openapi.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mammoth-cli
3
- Version: 2.0.40
3
+ Version: 2.0.41
4
4
  Summary: Command-line interface for the Mammoth Analytics platform
5
5
  Project-URL: homepage, https://mammoth.io
6
6
  Project-URL: repository, https://github.com/EdgeMetric/mammothsdk
@@ -6,7 +6,7 @@ not implement a second HTTP client and does not call private SDK members.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "2.0.40"
9
+ __version__ = "2.0.41"
10
10
 
11
11
  # The versioned machine output envelope contract.
12
12
  SCHEMA_VERSION = 1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mammoth-cli
3
- version: 2.0.40
3
+ version: 2.0.41
4
4
  description: "Use Mammoth Analytics from a terminal: install or authenticate the CLI, discover its live command contract, and safely manage projects, data, views, pipelines (join, merge, pivot, filter, clean), dashboards, exports, and handoffs."
5
5
  ---
6
6
 
@@ -93,10 +93,11 @@ report a real gap with the command you tried.
93
93
  Users often upload related files without saying how they relate ("make a
94
94
  dashboard from t_a and t_b"). Work it out from the data before you build:
95
95
 
96
- 1. Upload every file, then read every view in Mammoth (not the local
97
- files): `view get VIEW_ID` (columns, types, row count) and `view data get
98
- VIEW_ID`. The data read lists `column_warnings`: numbers or dates stored
99
- as text, and blanks, each with the fix.
96
+ 1. Upload every file. Each upload result carries a `view` preview of what
97
+ Mammoth made of the file (`view_id`, column types, sample rows,
98
+ `column_warnings`, and `before_dashboard`). Read every view in Mammoth, not
99
+ the local files: `view data get VIEW_ID` for each one. The warnings list
100
+ numbers or dates stored as text, and blanks, each with the fix.
100
101
  2. Find the keys. A column in one view whose values appear in a column of
101
102
  the other (`customer_id` and `id`, `order_ref` and `order_no`) is a
102
103
  foreign key; the view with many rows per key is the main one.
@@ -112,10 +113,14 @@ dashboard from t_a and t_b"). Work it out from the data before you build:
112
113
  result's `join_check` gives `match_rate`, `unmatched_rows` and
113
114
  `unmatched_keys`; put them in your report, and stop to compare the keys
114
115
  if more than a few rows found no match.
115
- 6. Build the dashboard from the joined view
116
- ([dashboards](references/recipes/dashboards.md)). If the data has money,
117
- show it: add `revenue` (`math`, `qty * price`, `new_column`) and chart
118
- its sum. Do not sum a unit price.
116
+ 6. If the data has money, add it before you make the dashboard: `revenue`
117
+ (`math`, `qty * price`, `new_column`), the `fix` in `before_dashboard`.
118
+ A dashboard sees only the columns the view had when it was made.
119
+ 7. Build the dashboard from the joined view
120
+ ([dashboards](references/recipes/dashboards.md)) and chart the sum of
121
+ revenue. Do not sum a unit price. `create-blank`, `canvas save` and
122
+ `pages add` return `deliverable_check`: fix each warning, or say in the
123
+ report why not.
119
124
 
120
125
  For the defaults that most pipelines use (new column or overwrite, `LEFT`
121
126
  joins, date steps), see
@@ -88,10 +88,19 @@ mammoth view transform math VIEW_ID --project PROJECT_ID \
88
88
  --input '{"dataset_id":DATASET_ID,"expression":"qty * price","new_column":"revenue"}'
89
89
  ```
90
90
 
91
+ - Add every column the board needs before `create-blank`. A dashboard sees
92
+ only the columns the view had when the dashboard was made: `pages add`
93
+ refuses a later column ("isn't a measure in this data"). If you added it
94
+ after, make a new dashboard and delete the old one if you made it for this
95
+ task. The upload result's `before_dashboard` names the column to add.
91
96
  - Put revenue in a KPI (`focus.kpis`, `agg` `sum`, `unit.prefix` for the
92
97
  currency) and in one or more charts (revenue by product, by segment, by
93
98
  month). Rows with an empty price give an empty revenue. Say how many there
94
99
  are in your report.
100
+ - `create-blank`, `canvas save` and `pages add` return `deliverable_check`:
101
+ `money_not_shown` (with the `math` command in `fix`), `unit_price_summed`,
102
+ and one `blank_values` entry for each column on the board that has blanks.
103
+ Fix each warning, or say in your report why you kept it.
95
104
 
96
105
  **Read the bindings back, then the numbers.** `dashboard canvas get` returns
97
106
  `data.meta.figures` — `"p1:kpi:2": {"descriptors": {"value": "<id>"}}` per
@@ -16,6 +16,8 @@ from typing import Any
16
16
  from mammoth.models.dashboards import AddPagesSpec
17
17
  from pydantic import ValidationError
18
18
 
19
+ from mammoth_cli.commands.view import view_profiles
20
+ from mammoth_cli.context import profiles
19
21
  from mammoth_cli.errors.envelope import (
20
22
  CODE_INVALID_ARGUMENT,
21
23
  CODE_MISSING_ARGUMENT,
@@ -25,6 +27,7 @@ from mammoth_cli.errors.envelope import (
25
27
  CliError,
26
28
  )
27
29
  from mammoth_cli.manifest.loader import command_by_id
30
+ from mammoth_cli.runtime import parents
28
31
  from mammoth_cli.runtime.confirm import (
29
32
  POLICY_CONFIRM_TARGET,
30
33
  POLICY_NONE,
@@ -33,10 +36,16 @@ from mammoth_cli.runtime.confirm import (
33
36
  enforce_confirmation,
34
37
  )
35
38
  from mammoth_cli.runtime.invocation import Invocation
36
- from mammoth_cli.runtime.session import open_service, require_project
39
+ from mammoth_cli.runtime.session import open_service, require_project, resolved_project
37
40
  from mammoth_cli.services.argspec import arg_spec
38
41
  from mammoth_cli.services.command_contract import bind_command_inputs
39
42
  from mammoth_cli.services.dashboard_pages import check_added_pages
43
+ from mammoth_cli.services.dashboard_review import (
44
+ CANVAS_GET,
45
+ NEW_COLUMN_NOTE,
46
+ has_profiles,
47
+ review,
48
+ )
40
49
  from mammoth_cli.services.positionals import resolve_positionals
41
50
 
42
51
  HandlerResult = tuple[Any, dict[str, Any]]
@@ -675,9 +684,68 @@ def generated_dashboard(invocation: Invocation) -> HandlerResult:
675
684
  if invocation.command_id == "dashboard.pages.add" and "body" in kwargs:
676
685
  requested = kwargs["body"].get("params", {}).get("pages", [])
677
686
  data = check_added_pages(service, positionals["dashboard_id"], requested, data)
687
+ if invocation.command_id in _REVIEWED_COMMANDS:
688
+ data = _with_deliverable_check(invocation, service, auth, positionals, data)
678
689
  return data, _meta(invocation, auth.workspace_id)
679
690
 
680
691
 
692
+ #: Authoring steps whose result carries ``deliverable_check``.
693
+ _REVIEWED_COMMANDS = frozenset(
694
+ {"dashboard.create-blank", "dashboard.canvas.save", "dashboard.pages.add"}
695
+ )
696
+
697
+
698
+ def _with_deliverable_check(
699
+ invocation: Invocation, service: Any, auth: Any, positionals: dict[str, Any], data: Any
700
+ ) -> Any:
701
+ """Add ``deliverable_check`` (money shown? blanks decided?) to an authoring result.
702
+
703
+ One canvas read; advice only, so a failed read leaves the result as it was.
704
+ """
705
+ if hasattr(data, "model_dump"):
706
+ data = data.model_dump(mode="json")
707
+ if not isinstance(data, dict):
708
+ return data
709
+ dashboard_id = positionals.get("dashboard_id") or data.get("id")
710
+ if not isinstance(dashboard_id, int):
711
+ return data
712
+ try:
713
+ canvas_doc = service.call(CANVAS_GET, dashboard_id=dashboard_id)
714
+ if hasattr(canvas_doc, "model_dump"):
715
+ canvas_doc = canvas_doc.model_dump(mode="json")
716
+ source = (canvas_doc.get("canvas") or {}).get("dataset") or {}
717
+ view_id = source.get("dataview_id")
718
+ profile = invocation.profile or profiles.get_selected()
719
+ dataset_id = (
720
+ parents.lookup(profile, auth.workspace_id, view_id)
721
+ if isinstance(view_id, int)
722
+ else None
723
+ )
724
+ fallback = None
725
+ if not has_profiles(canvas_doc) and isinstance(view_id, int) and dataset_id is not None:
726
+ fallback = view_profiles(service, dataset_id, view_id, resolved_project(invocation))
727
+ warnings = review(canvas_doc, dataset_id, fallback)
728
+ except Exception: # noqa: BLE001 -- advice must never fail the authoring step
729
+ return data
730
+ if warnings:
731
+ data = {
732
+ **data,
733
+ "deliverable_check": {
734
+ "warnings": warnings,
735
+ "note": (
736
+ "Fix these before you report the dashboard as done, or say in your "
737
+ "report why not."
738
+ + (
739
+ " " + NEW_COLUMN_NOTE
740
+ if any("fix" in warning for warning in warnings)
741
+ else ""
742
+ )
743
+ ),
744
+ },
745
+ }
746
+ return data
747
+
748
+
681
749
  def dashboard_assess_twb(invocation: Invocation) -> HandlerResult:
682
750
  """Assess a local Tableau workbook through the SDK multipart seam."""
683
751
  file_path = _require_str_positional(invocation, "file")
@@ -15,6 +15,8 @@ from collections.abc import Callable
15
15
  from pathlib import Path
16
16
  from typing import Any
17
17
 
18
+ from mammoth_cli.commands.view import upload_preview
19
+ from mammoth_cli.context import profiles
18
20
  from mammoth_cli.errors.envelope import (
19
21
  CODE_INVALID_ARGUMENT,
20
22
  CODE_MISSING_ARGUMENT,
@@ -24,6 +26,7 @@ from mammoth_cli.errors.envelope import (
24
26
  CliError,
25
27
  )
26
28
  from mammoth_cli.manifest.loader import command_by_id
29
+ from mammoth_cli.runtime import parents
27
30
  from mammoth_cli.runtime.confirm import (
28
31
  POLICY_CONFIRM_TARGET,
29
32
  POLICY_PROMPT_OR_YES,
@@ -226,6 +229,18 @@ def file_upload(invocation: Invocation) -> HandlerResult:
226
229
  # ambiguous date column lands in ``need_action`` with no view. Report
227
230
  # the status the platform holds for each dataset, not an assumed one.
228
231
  result = _upload_result(data, lambda dataset_id: _dataset_status(service, dataset_id))
232
+ project_id = resolved_project(invocation)
233
+ for entry in result["datasets"]:
234
+ if entry.get("status") != "ready":
235
+ continue
236
+ preview = upload_preview(service, int(entry["id"]), project_id)
237
+ if preview is not None:
238
+ entry["view"] = preview
239
+ parents.remember(
240
+ invocation.profile or profiles.get_selected(),
241
+ auth.workspace_id,
242
+ {preview["view_id"]: int(entry["id"])},
243
+ )
229
244
  return result, _meta(invocation, auth.workspace_id, resolved_project(invocation))
230
245
 
231
246
 
@@ -46,6 +46,7 @@ from mammoth_cli.runtime.confirm import (
46
46
  from mammoth_cli.runtime.invocation import Invocation
47
47
  from mammoth_cli.runtime.session import open_service, require_project
48
48
  from mammoth_cli.services.conditions import CONDITION_KWARG, compile_condition
49
+ from mammoth_cli.services.dashboard_review import UPLOAD_NOTE, upload_hints
49
50
  from mammoth_cli.services.data_quality import column_warnings
50
51
 
51
52
  HandlerResult = tuple[Any, dict[str, Any]]
@@ -2065,3 +2066,108 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
2065
2066
  + (f" --project {int(target_project)}" if target_project is not None else ""),
2066
2067
  }
2067
2068
  return data, _meta(invocation, auth.workspace_id, project_id)
2069
+
2070
+
2071
+ _DATAVIEW_LIST_SYMBOL = "mammoth.api.dataviews.DataviewsAPI.list"
2072
+ #: Rows an upload shows of each new view (enough to see keys and formats).
2073
+ _UPLOAD_SAMPLE_ROWS = 3
2074
+
2075
+
2076
+ def upload_preview(service: Any, dataset_id: int, project_id: int | None) -> dict[str, Any] | None:
2077
+ """The first view of a new dataset: columns, types, sample rows, warnings.
2078
+
2079
+ ``before_dashboard`` names the columns to add before any dashboard is
2080
+ made (revenue from a unit price and a quantity), since a dashboard does
2081
+ not see columns added after it.
2082
+
2083
+ An agent that has just uploaded files tends to read the local copies
2084
+ instead of the data in Mammoth, and so misses what Mammoth made of them
2085
+ (a price read as TEXT, blanks). The upload result carries the answer.
2086
+ Best effort: any failure returns ``None`` and the upload result is
2087
+ unchanged.
2088
+ """
2089
+ try:
2090
+ listing = service.call(_DATAVIEW_LIST_SYMBOL, dataset_id=dataset_id, project_id=project_id)
2091
+ views = listing.get("dataviews") if isinstance(listing, dict) else None
2092
+ if not isinstance(views, list) or not views or not isinstance(views[0], dict):
2093
+ return None
2094
+ record = apply_column_renames(views[0])
2095
+ view_id = record.get("id")
2096
+ if not isinstance(view_id, int):
2097
+ return None
2098
+ mapping: dict[str, str] = {}
2099
+ types: dict[str, str] = {}
2100
+ for column in record.get(_METADATA_KEY) or []:
2101
+ if not isinstance(column, dict):
2102
+ continue
2103
+ internal = column.get(_INTERNAL_NAME_KEY)
2104
+ display = column.get(_DISPLAY_NAME_KEY)
2105
+ if isinstance(internal, str) and isinstance(display, str):
2106
+ mapping[internal] = display
2107
+ types[display] = str(column.get("type") or "")
2108
+ page = service.call(
2109
+ _DATA_PAGE_SYMBOL, dataset_id=dataset_id, dataview_id=view_id, project_id=project_id
2110
+ )
2111
+ page = _relabel_columns(service, dataset_id, view_id, project_id, page, mapping)
2112
+ rows = page.get(_ROWS_KEY) if isinstance(page, dict) else None
2113
+ rows = [row for row in rows or [] if isinstance(row, dict)]
2114
+ except Exception: # noqa: BLE001 -- a preview must never fail the upload
2115
+ return None
2116
+ preview: dict[str, Any] = {
2117
+ "view_id": view_id,
2118
+ "row_count": record.get("row_count"),
2119
+ "columns": types,
2120
+ "sample_rows": rows[:_UPLOAD_SAMPLE_ROWS],
2121
+ }
2122
+ try:
2123
+ warnings = column_warnings(rows, types, view_id) if rows else []
2124
+ except Exception: # noqa: BLE001
2125
+ warnings = []
2126
+ if warnings:
2127
+ preview["column_warnings"] = warnings
2128
+ try:
2129
+ text_numbers = [
2130
+ str(w.get("column"))
2131
+ for w in warnings
2132
+ if isinstance(w, dict) and w.get("issue") == "numbers_stored_as_text"
2133
+ ]
2134
+ hints = upload_hints(
2135
+ view_id, record.get(_METADATA_KEY) or [], rows, dataset_id, text_numbers
2136
+ )
2137
+ except Exception: # noqa: BLE001
2138
+ hints = []
2139
+ if hints:
2140
+ preview["before_dashboard"] = {"warnings": hints, "note": UPLOAD_NOTE}
2141
+ return preview
2142
+
2143
+
2144
+ def view_profiles(
2145
+ service: Any, dataset_id: int, view_id: int, project_id: int | None
2146
+ ) -> list[dict[str, Any]] | None:
2147
+ """Column profile of a view (types and blank shares) for the dashboard check."""
2148
+ from mammoth_cli.services.dashboard_review import profiles_from_view
2149
+
2150
+ try:
2151
+ info = apply_column_renames(
2152
+ service.call(
2153
+ _DATAVIEW_GET_SYMBOL,
2154
+ dataset_id=dataset_id,
2155
+ dataview_id=view_id,
2156
+ project_id=project_id,
2157
+ )
2158
+ )
2159
+ metadata = [c for c in (info or {}).get(_METADATA_KEY) or [] if isinstance(c, dict)]
2160
+ mapping = {
2161
+ c[_INTERNAL_NAME_KEY]: c[_DISPLAY_NAME_KEY]
2162
+ for c in metadata
2163
+ if isinstance(c.get(_INTERNAL_NAME_KEY), str)
2164
+ and isinstance(c.get(_DISPLAY_NAME_KEY), str)
2165
+ }
2166
+ page = service.call(
2167
+ _DATA_PAGE_SYMBOL, dataset_id=dataset_id, dataview_id=view_id, project_id=project_id
2168
+ )
2169
+ page = _relabel_columns(service, dataset_id, view_id, project_id, page, mapping)
2170
+ rows = page.get(_ROWS_KEY) if isinstance(page, dict) else None
2171
+ return profiles_from_view(metadata, rows or [])
2172
+ except Exception: # noqa: BLE001 -- advice only
2173
+ return None
@@ -290,6 +290,11 @@ def _dispatch_view(
290
290
  reject_pipeline_reference_errors(service, view_id, dataset_id, response)
291
291
  raise
292
292
  reject_pipeline_reference_errors(service, view_id, dataset_id, data)
293
+ if dataset_id is not None:
294
+ # Later commands that know only the view (a dashboard's check) use it.
295
+ parents.remember(
296
+ _profile_name(invocation), auth.workspace_id, {view_id: int(dataset_id)}
297
+ )
293
298
  if after is not None and dataset_id is not None:
294
299
  data = after(service, int(dataset_id), state, data)
295
300
  return data, _meta(invocation, auth.workspace_id)
@@ -513,9 +518,12 @@ def view_get(invocation: Invocation) -> HandlerResult:
513
518
  "dataview_id": view_id,
514
519
  # Server-side projection; the brief set (plus the display
515
520
  # properties that carry column renames) unless the caller asks.
516
- "fields": document.get("fields")
517
- or ",".join(
518
- [key for key in BRIEF_VIEW_FIELDS if key != "dataset_id"] + ["display_properties"]
521
+ "fields": (
522
+ document.get("fields")
523
+ or ",".join(
524
+ [key for key in BRIEF_VIEW_FIELDS if key != "dataset_id"]
525
+ + ["display_properties"]
526
+ )
519
527
  ),
520
528
  }
521
529
  with open_service(invocation) as (service, auth):
@@ -0,0 +1,284 @@
1
+ """Check a dashboard against what its data holds, after each authoring step.
2
+
3
+ The skill tells an agent to put the money on the board and to decide on
4
+ blanks, but an agent that authors the canvas directly never reads that part
5
+ (cold-agent eval, CLI 2.0.40: counts only, blanks undecided). The check runs
6
+ where the agent already looks: the result of ``create-blank``, ``canvas
7
+ save`` and ``pages add``. It uses the column profile the dashboard backend
8
+ computes for the canvas (``plan.hints._profiles``), so it costs one read.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import re
15
+ from collections.abc import Iterable, Mapping
16
+ from typing import Any
17
+
18
+ CANVAS_GET = "mammoth.api.dashboards.DashboardsAPI.canvas_get"
19
+
20
+ #: A dashboard reads the view as it was when the dashboard was made; a column
21
+ #: added later is not a measure there (release, CLI 2.0.41: pages add refused
22
+ #: ``revenue`` on a dashboard made before it, and ``swap-data`` did not always
23
+ #: pick it up). A dashboard made after the column sees it.
24
+ NEW_COLUMN_NOTE = (
25
+ "A dashboard sees only the columns the view had when the dashboard was made. "
26
+ "Run each fix first, then make a new dashboard (dashboard create-blank) and build "
27
+ "on that one; delete this one if you made it for this task."
28
+ )
29
+ #: The same fact, for the upload result, where no dashboard exists yet.
30
+ UPLOAD_NOTE = (
31
+ "Run these before you make a dashboard: a dashboard sees only the columns "
32
+ "the view had when the dashboard was made."
33
+ )
34
+
35
+ _MONEY = re.compile(
36
+ r"price|amount|revenue|sales|cost|spend|fee|income|profit|margin|total|value|gmv|arr|mrr",
37
+ re.IGNORECASE,
38
+ )
39
+ _UNIT = re.compile(r"price|rate|unit|per[_ ]", re.IGNORECASE)
40
+ _QUANTITY = re.compile(r"^(qty|quantity|units?|count|volume|pieces|items?)\b", re.IGNORECASE)
41
+ _MEASURE_KEYS = ("measure", "measure2", "measure_x", "measure_y", "measure_size")
42
+ _LIST_MEASURE_KEYS = ("measures", "extra_measures")
43
+
44
+
45
+ def _profiles(canvas_doc: Mapping[str, Any]) -> list[dict[str, Any]]:
46
+ plan = canvas_doc.get("plan")
47
+ hints = plan.get("hints") if isinstance(plan, Mapping) else None
48
+ profiles = hints.get("_profiles") if isinstance(hints, Mapping) else None
49
+ return [p for p in profiles or [] if isinstance(p, dict) and isinstance(p.get("name"), str)]
50
+
51
+
52
+ def _charts(canvas: Mapping[str, Any]) -> Iterable[tuple[Mapping[str, Any], Mapping[str, Any]]]:
53
+ """Yield ``(container, figure)`` for every authored figure and focus."""
54
+ containers = [canvas, *[p for p in canvas.get("pages") or [] if isinstance(p, Mapping)]]
55
+ for container in containers:
56
+ focus = container.get("focus")
57
+ if isinstance(focus, Mapping):
58
+ yield container, focus
59
+ for figure in container.get("added") or []:
60
+ if isinstance(figure, Mapping):
61
+ yield container, figure
62
+
63
+
64
+ def _used(canvas: Mapping[str, Any]) -> tuple[set[str], set[str], list[tuple[str, str]]]:
65
+ """Measures and dimensions the board uses, and ``(measure, agg)`` pairs."""
66
+ derived = {
67
+ str(d.get("id")): [d.get("numerator"), d.get("denominator")]
68
+ for d in canvas.get("derived") or []
69
+ if isinstance(d, Mapping)
70
+ }
71
+ measures: set[str] = set()
72
+ dims: set[str] = set()
73
+ aggs: list[tuple[str, str]] = []
74
+
75
+ def add(name: Any, agg: Any) -> None:
76
+ if not isinstance(name, str):
77
+ return
78
+ for part in derived.get(name, [name]):
79
+ if isinstance(part, str):
80
+ measures.add(part)
81
+ aggs.append((part, str(agg or "sum").lower()))
82
+
83
+ for _, figure in _charts(canvas):
84
+ agg = figure.get("agg")
85
+ for key in _MEASURE_KEYS:
86
+ add(figure.get(key), agg)
87
+ for key in _LIST_MEASURE_KEYS:
88
+ for name in figure.get(key) or []:
89
+ add(name.get("field") if isinstance(name, Mapping) else name, agg)
90
+ for kpi in figure.get("kpis") or []:
91
+ if isinstance(kpi, Mapping):
92
+ add(kpi.get("field"), kpi.get("agg"))
93
+ for key in ("dim", "col_dim", "row_dim", "series", "date_field"):
94
+ if isinstance(figure.get(key), str):
95
+ dims.add(figure[key])
96
+ return measures, dims, aggs
97
+
98
+
99
+ def _math_fix(view_id: Any, dataset_id: Any, quantity: str, price: str) -> str:
100
+ spec = json.dumps(
101
+ {
102
+ "dataset_id": dataset_id if dataset_id is not None else "DATASET_ID",
103
+ "expression": f"{quantity} * {price}",
104
+ "new_column": "revenue",
105
+ }
106
+ )
107
+ return f"mammoth view transform math {view_id} --input '{spec}'"
108
+
109
+
110
+ def profiles_from_view(
111
+ metadata: Iterable[Mapping[str, Any]], rows: Iterable[Mapping[str, Any]]
112
+ ) -> list[dict[str, Any]]:
113
+ """A stand-in for ``plan.hints._profiles`` from view metadata and a data page.
114
+
115
+ A blank canvas has no profile until its first bake, so ``create-blank``
116
+ builds one: NUMERIC columns are measures, and ``nullRate`` is the share of
117
+ blank values in the rows given (display-name keyed).
118
+ """
119
+ materialised = [row for row in rows if isinstance(row, Mapping)]
120
+ profiles: list[dict[str, Any]] = []
121
+ for column in metadata:
122
+ name = column.get("display_name")
123
+ if not isinstance(name, str):
124
+ continue
125
+ kind = str(column.get("type") or "").upper()
126
+ profile: dict[str, Any] = {
127
+ "name": name,
128
+ "type": "measure" if kind == "NUMERIC" else "date" if kind == "DATE" else "dimension",
129
+ }
130
+ # Only rows that carry the column count; a column the page left out
131
+ # is unknown, not blank.
132
+ values = [row[name] for row in materialised if name in row]
133
+ blank = sum(1 for v in values if v is None or (isinstance(v, str) and not v.strip()))
134
+ if blank:
135
+ profile["nullRate"] = blank / len(values)
136
+ profiles.append(profile)
137
+ return profiles
138
+
139
+
140
+ def review(
141
+ canvas_doc: Any,
142
+ dataset_id: int | None = None,
143
+ fallback_profiles: list[dict[str, Any]] | None = None,
144
+ ) -> list[dict[str, Any]]:
145
+ """Return warnings about money and blanks for a canvas read.
146
+
147
+ Args:
148
+ canvas_doc: A ``dashboard canvas get`` payload (``canvas``, ``plan``).
149
+ dataset_id: The view's parent dataset, for the suggested command.
150
+
151
+ Returns:
152
+ ``money_not_shown``, ``unit_price_summed`` and ``blank_values``
153
+ records (``issue``, ``detail``, optional ``fix``); empty when none.
154
+ """
155
+ if not isinstance(canvas_doc, Mapping):
156
+ return []
157
+ canvas = canvas_doc.get("canvas")
158
+ if not isinstance(canvas, Mapping):
159
+ return []
160
+ profiles = _profiles(canvas_doc) or list(fallback_profiles or [])
161
+ if not profiles:
162
+ return []
163
+ source = canvas.get("dataset")
164
+ view_id = source.get("dataview_id") if isinstance(source, Mapping) else None
165
+ view_ref = view_id if view_id is not None else "VIEW_ID"
166
+ measures, dims, aggs = _used(canvas)
167
+ numeric = [p for p in profiles if p.get("type") == "measure"]
168
+ unit_prices = [
169
+ p["name"]
170
+ for p in numeric
171
+ if p.get("perUnit") is True or (_UNIT.search(p["name"]) and _MONEY.search(p["name"]))
172
+ ]
173
+ money = [
174
+ p["name"] for p in numeric if _MONEY.search(p["name"]) and p["name"] not in unit_prices
175
+ ]
176
+ quantities = [
177
+ p["name"] for p in numeric if p.get("semantic") == "count" or _QUANTITY.search(p["name"])
178
+ ]
179
+ warnings: list[dict[str, Any]] = []
180
+ shows_money = any(name in measures for name in money + unit_prices)
181
+ if (money or unit_prices) and not shows_money:
182
+ if money:
183
+ detail = (
184
+ f"The view has money ({', '.join(money)}) but no chart or KPI shows it. "
185
+ f"Add a KPI and a chart with the sum of {money[0]}."
186
+ )
187
+ warnings.append({"issue": "money_not_shown", "detail": detail})
188
+ else:
189
+ record: dict[str, Any] = {
190
+ "issue": "money_not_shown",
191
+ "detail": (
192
+ f"The view has a unit price ({', '.join(unit_prices)}) but no chart shows "
193
+ "money. A unit price is not revenue: "
194
+ + (
195
+ f"add revenue = {quantities[0]} * {unit_prices[0]}, then chart its sum."
196
+ if quantities
197
+ else "chart revenue, not the sum of the price."
198
+ )
199
+ ),
200
+ }
201
+ if quantities:
202
+ record["fix"] = _math_fix(view_ref, dataset_id, quantities[0], unit_prices[0])
203
+ warnings.append(record)
204
+ summed = sorted({name for name, agg in aggs if name in unit_prices and agg == "sum"})
205
+ if summed:
206
+ record = {
207
+ "issue": "unit_price_summed",
208
+ "detail": (
209
+ f"A chart sums the unit price {summed[0]}; that total means nothing. "
210
+ + (
211
+ f"Chart the sum of revenue = {quantities[0]} * {summed[0]} instead."
212
+ if quantities
213
+ else "Use avg, or chart revenue instead."
214
+ )
215
+ ),
216
+ }
217
+ if quantities:
218
+ record["fix"] = _math_fix(view_ref, dataset_id, quantities[0], summed[0])
219
+ warnings.append(record)
220
+ shown = measures | dims
221
+ for profile in profiles:
222
+ name = profile["name"]
223
+ rate = profile.get("nullRate") or profile.get("emptyRate") or 0
224
+ if not isinstance(rate, (int, float)) or rate <= 0:
225
+ continue
226
+ if shown and name not in shown:
227
+ continue
228
+ warnings.append(
229
+ {
230
+ "issue": "blank_values",
231
+ "column": name,
232
+ "detail": (
233
+ f"{round(rate * 100)}% of {name} is blank. Say in your report what you "
234
+ "did: filled, filtered, or kept (and how the board shows them)."
235
+ ),
236
+ }
237
+ )
238
+ return warnings
239
+
240
+
241
+ def upload_hints(
242
+ view_id: Any,
243
+ metadata: Iterable[Mapping[str, Any]],
244
+ rows: Iterable[Mapping[str, Any]],
245
+ dataset_id: Any = None,
246
+ text_numbers: Iterable[str] = (),
247
+ ) -> list[dict[str, Any]]:
248
+ """The money warnings for a new view, before any dashboard exists.
249
+
250
+ The fix is cheapest here: a column added before ``create-blank`` is on
251
+ the dashboard, one added after is not (``NEW_COLUMN_NOTE``).
252
+ ``text_numbers`` are the columns ``column_warnings`` found to be numbers
253
+ stored as text; they count as numbers here, and the warning says to
254
+ convert them first.
255
+ """
256
+ pending = set(text_numbers)
257
+ profiles = [
258
+ {**profile, "type": "measure"} if profile["name"] in pending else profile
259
+ for profile in profiles_from_view(metadata, rows)
260
+ ]
261
+ canvas_doc = {"canvas": {"dataset": {"dataview_id": view_id}}}
262
+ warnings = []
263
+ # Blanks are already in the upload's column_warnings.
264
+ for warning in review(canvas_doc, dataset_id, profiles):
265
+ if warning["issue"] == "blank_values":
266
+ continue
267
+ # No board exists yet: drop the "no chart shows it" clause.
268
+ detail = (
269
+ str(warning["detail"])
270
+ .replace(" but no chart shows money", "")
271
+ .replace(" but no chart or KPI shows it", "")
272
+ )
273
+ convert = sorted(name for name in pending if name in detail)
274
+ if convert:
275
+ detail = (
276
+ f"Convert {', '.join(convert)} to NUMERIC first (see column_warnings). " + detail
277
+ )
278
+ warnings.append({**warning, "detail": detail})
279
+ return warnings
280
+
281
+
282
+ def has_profiles(canvas_doc: Any) -> bool:
283
+ """Whether a canvas read carries the backend's column profile."""
284
+ return isinstance(canvas_doc, Mapping) and bool(_profiles(canvas_doc))
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mammoth-cli"
3
- version = "2.0.40"
3
+ version = "2.0.41"
4
4
  description = "Command-line interface for the Mammoth Analytics platform"
5
5
  authors = [{name = "Mammoth Analytics", email = "support@mammoth.io"}]
6
6
  readme = "README.md"
File without changes
File without changes
File without changes