@ultracart/bq-skill 0.1.2

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 (303) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +952 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +42 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/alarm.d.ts +3 -0
  8. package/dist/commands/alarm.d.ts.map +1 -0
  9. package/dist/commands/alarm.js +146 -0
  10. package/dist/commands/alarm.js.map +1 -0
  11. package/dist/commands/config.d.ts +3 -0
  12. package/dist/commands/config.d.ts.map +1 -0
  13. package/dist/commands/config.js +749 -0
  14. package/dist/commands/config.js.map +1 -0
  15. package/dist/commands/deck.d.ts +3 -0
  16. package/dist/commands/deck.d.ts.map +1 -0
  17. package/dist/commands/deck.js +567 -0
  18. package/dist/commands/deck.js.map +1 -0
  19. package/dist/commands/dry-run.d.ts +3 -0
  20. package/dist/commands/dry-run.d.ts.map +1 -0
  21. package/dist/commands/dry-run.js +105 -0
  22. package/dist/commands/dry-run.js.map +1 -0
  23. package/dist/commands/history.d.ts +3 -0
  24. package/dist/commands/history.d.ts.map +1 -0
  25. package/dist/commands/history.js +127 -0
  26. package/dist/commands/history.js.map +1 -0
  27. package/dist/commands/init.d.ts +3 -0
  28. package/dist/commands/init.d.ts.map +1 -0
  29. package/dist/commands/init.js +302 -0
  30. package/dist/commands/init.js.map +1 -0
  31. package/dist/commands/install-skill.d.ts +3 -0
  32. package/dist/commands/install-skill.d.ts.map +1 -0
  33. package/dist/commands/install-skill.js +132 -0
  34. package/dist/commands/install-skill.js.map +1 -0
  35. package/dist/commands/list.d.ts +3 -0
  36. package/dist/commands/list.d.ts.map +1 -0
  37. package/dist/commands/list.js +89 -0
  38. package/dist/commands/list.js.map +1 -0
  39. package/dist/commands/query.d.ts +3 -0
  40. package/dist/commands/query.d.ts.map +1 -0
  41. package/dist/commands/query.js +152 -0
  42. package/dist/commands/query.js.map +1 -0
  43. package/dist/commands/render.d.ts +3 -0
  44. package/dist/commands/render.d.ts.map +1 -0
  45. package/dist/commands/render.js +107 -0
  46. package/dist/commands/render.js.map +1 -0
  47. package/dist/commands/run-all.d.ts +3 -0
  48. package/dist/commands/run-all.d.ts.map +1 -0
  49. package/dist/commands/run-all.js +285 -0
  50. package/dist/commands/run-all.js.map +1 -0
  51. package/dist/commands/run.d.ts +3 -0
  52. package/dist/commands/run.d.ts.map +1 -0
  53. package/dist/commands/run.js +321 -0
  54. package/dist/commands/run.js.map +1 -0
  55. package/dist/commands/schema.d.ts +3 -0
  56. package/dist/commands/schema.d.ts.map +1 -0
  57. package/dist/commands/schema.js +271 -0
  58. package/dist/commands/schema.js.map +1 -0
  59. package/dist/commands/validate.d.ts +3 -0
  60. package/dist/commands/validate.d.ts.map +1 -0
  61. package/dist/commands/validate.js +109 -0
  62. package/dist/commands/validate.js.map +1 -0
  63. package/dist/lib/alarm-notify.d.ts +16 -0
  64. package/dist/lib/alarm-notify.d.ts.map +1 -0
  65. package/dist/lib/alarm-notify.js +312 -0
  66. package/dist/lib/alarm-notify.js.map +1 -0
  67. package/dist/lib/alarm-state.d.ts +26 -0
  68. package/dist/lib/alarm-state.d.ts.map +1 -0
  69. package/dist/lib/alarm-state.js +118 -0
  70. package/dist/lib/alarm-state.js.map +1 -0
  71. package/dist/lib/alarm.d.ts +28 -0
  72. package/dist/lib/alarm.d.ts.map +1 -0
  73. package/dist/lib/alarm.js +215 -0
  74. package/dist/lib/alarm.js.map +1 -0
  75. package/dist/lib/analysis.d.ts +16 -0
  76. package/dist/lib/analysis.d.ts.map +1 -0
  77. package/dist/lib/analysis.js +134 -0
  78. package/dist/lib/analysis.js.map +1 -0
  79. package/dist/lib/bigquery.d.ts +55 -0
  80. package/dist/lib/bigquery.d.ts.map +1 -0
  81. package/dist/lib/bigquery.js +321 -0
  82. package/dist/lib/bigquery.js.map +1 -0
  83. package/dist/lib/config-writer.d.ts +3 -0
  84. package/dist/lib/config-writer.d.ts.map +1 -0
  85. package/dist/lib/config-writer.js +60 -0
  86. package/dist/lib/config-writer.js.map +1 -0
  87. package/dist/lib/config.d.ts +63 -0
  88. package/dist/lib/config.d.ts.map +1 -0
  89. package/dist/lib/config.js +151 -0
  90. package/dist/lib/config.js.map +1 -0
  91. package/dist/lib/dashboard.d.ts +3 -0
  92. package/dist/lib/dashboard.d.ts.map +1 -0
  93. package/dist/lib/dashboard.js +468 -0
  94. package/dist/lib/dashboard.js.map +1 -0
  95. package/dist/lib/deck.d.ts +58 -0
  96. package/dist/lib/deck.d.ts.map +1 -0
  97. package/dist/lib/deck.js +232 -0
  98. package/dist/lib/deck.js.map +1 -0
  99. package/dist/lib/deliver-email.d.ts +14 -0
  100. package/dist/lib/deliver-email.d.ts.map +1 -0
  101. package/dist/lib/deliver-email.js +360 -0
  102. package/dist/lib/deliver-email.js.map +1 -0
  103. package/dist/lib/deliver-slack.d.ts +6 -0
  104. package/dist/lib/deliver-slack.d.ts.map +1 -0
  105. package/dist/lib/deliver-slack.js +73 -0
  106. package/dist/lib/deliver-slack.js.map +1 -0
  107. package/dist/lib/deliver.d.ts +10 -0
  108. package/dist/lib/deliver.d.ts.map +1 -0
  109. package/dist/lib/deliver.js +95 -0
  110. package/dist/lib/deliver.js.map +1 -0
  111. package/dist/lib/llm/anthropic.d.ts +7 -0
  112. package/dist/lib/llm/anthropic.d.ts.map +1 -0
  113. package/dist/lib/llm/anthropic.js +69 -0
  114. package/dist/lib/llm/anthropic.js.map +1 -0
  115. package/dist/lib/llm/bedrock.d.ts +7 -0
  116. package/dist/lib/llm/bedrock.d.ts.map +1 -0
  117. package/dist/lib/llm/bedrock.js +67 -0
  118. package/dist/lib/llm/bedrock.js.map +1 -0
  119. package/dist/lib/llm/gemini.d.ts +7 -0
  120. package/dist/lib/llm/gemini.d.ts.map +1 -0
  121. package/dist/lib/llm/gemini.js +67 -0
  122. package/dist/lib/llm/gemini.js.map +1 -0
  123. package/dist/lib/llm/index.d.ts +5 -0
  124. package/dist/lib/llm/index.d.ts.map +1 -0
  125. package/dist/lib/llm/index.js +61 -0
  126. package/dist/lib/llm/index.js.map +1 -0
  127. package/dist/lib/llm/models.d.ts +5 -0
  128. package/dist/lib/llm/models.d.ts.map +1 -0
  129. package/dist/lib/llm/models.js +33 -0
  130. package/dist/lib/llm/models.js.map +1 -0
  131. package/dist/lib/llm/openai.d.ts +8 -0
  132. package/dist/lib/llm/openai.d.ts.map +1 -0
  133. package/dist/lib/llm/openai.js +48 -0
  134. package/dist/lib/llm/openai.js.map +1 -0
  135. package/dist/lib/llm/provider.d.ts +28 -0
  136. package/dist/lib/llm/provider.d.ts.map +1 -0
  137. package/dist/lib/llm/provider.js +3 -0
  138. package/dist/lib/llm/provider.js.map +1 -0
  139. package/dist/lib/manifest.d.ts +95 -0
  140. package/dist/lib/manifest.d.ts.map +1 -0
  141. package/dist/lib/manifest.js +105 -0
  142. package/dist/lib/manifest.js.map +1 -0
  143. package/dist/lib/params.d.ts +27 -0
  144. package/dist/lib/params.d.ts.map +1 -0
  145. package/dist/lib/params.js +192 -0
  146. package/dist/lib/params.js.map +1 -0
  147. package/dist/lib/pdf.d.ts +8 -0
  148. package/dist/lib/pdf.d.ts.map +1 -0
  149. package/dist/lib/pdf.js +86 -0
  150. package/dist/lib/pdf.js.map +1 -0
  151. package/dist/lib/renderer.d.ts +17 -0
  152. package/dist/lib/renderer.d.ts.map +1 -0
  153. package/dist/lib/renderer.js +166 -0
  154. package/dist/lib/renderer.js.map +1 -0
  155. package/dist/lib/schema-filter.d.ts +3 -0
  156. package/dist/lib/schema-filter.d.ts.map +1 -0
  157. package/dist/lib/schema-filter.js +49 -0
  158. package/dist/lib/schema-filter.js.map +1 -0
  159. package/dist/lib/template.d.ts +4 -0
  160. package/dist/lib/template.d.ts.map +1 -0
  161. package/dist/lib/template.js +37 -0
  162. package/dist/lib/template.js.map +1 -0
  163. package/dist/lib/validator.d.ts +8 -0
  164. package/dist/lib/validator.d.ts.map +1 -0
  165. package/dist/lib/validator.js +88 -0
  166. package/dist/lib/validator.js.map +1 -0
  167. package/package.json +59 -0
  168. package/schemas/deck.schema.json +122 -0
  169. package/schemas/report-manifest.schema.json +309 -0
  170. package/schemas/tables/README.md +45 -0
  171. package/schemas/tables/ultracart_dw/uc_affiliate_clicks.json +186 -0
  172. package/schemas/tables/ultracart_dw/uc_affiliate_commission_groups.json +317 -0
  173. package/schemas/tables/ultracart_dw/uc_affiliate_ledgers.json +404 -0
  174. package/schemas/tables/ultracart_dw/uc_affiliate_network_pixel_postback_logs.json +166 -0
  175. package/schemas/tables/ultracart_dw/uc_affiliate_network_pixels.json +106 -0
  176. package/schemas/tables/ultracart_dw/uc_affiliate_payments.json +204 -0
  177. package/schemas/tables/ultracart_dw/uc_affiliate_postback_logs.json +90 -0
  178. package/schemas/tables/ultracart_dw/uc_affiliates.json +425 -0
  179. package/schemas/tables/ultracart_dw/uc_auto_orders.json +13848 -0
  180. package/schemas/tables/ultracart_dw/uc_cart_abandons.json +3971 -0
  181. package/schemas/tables/ultracart_dw/uc_conversation_pbx_calls.json +374 -0
  182. package/schemas/tables/ultracart_dw/uc_conversations.json +374 -0
  183. package/schemas/tables/ultracart_dw/uc_coupons.json +1893 -0
  184. package/schemas/tables/ultracart_dw/uc_customers.json +11886 -0
  185. package/schemas/tables/ultracart_dw/uc_fraud_rules.json +239 -0
  186. package/schemas/tables/ultracart_dw/uc_gift_certificates.json +135 -0
  187. package/schemas/tables/ultracart_dw/uc_item_inventory_history.json +79 -0
  188. package/schemas/tables/ultracart_dw/uc_items.json +4437 -0
  189. package/schemas/tables/ultracart_dw/uc_orders.json +6629 -0
  190. package/schemas/tables/ultracart_dw/uc_rotating_transaction_gateway_history.json +271 -0
  191. package/schemas/tables/ultracart_dw/uc_rotating_transaction_gateways.json +416 -0
  192. package/schemas/tables/ultracart_dw/uc_shipping_methods.json +1372 -0
  193. package/schemas/tables/ultracart_dw/uc_storefront_customers.json +261 -0
  194. package/schemas/tables/ultracart_dw/uc_storefront_experiments.json +386 -0
  195. package/schemas/tables/ultracart_dw/uc_storefront_pages.json +513 -0
  196. package/schemas/tables/ultracart_dw/uc_storefront_upsell_offer_events.json +338 -0
  197. package/schemas/tables/ultracart_dw/uc_storefront_upsell_offers.json +431 -0
  198. package/schemas/tables/ultracart_dw/uc_storefront_upsell_paths.json +163 -0
  199. package/schemas/tables/ultracart_dw/uc_storefronts.json +62 -0
  200. package/schemas/tables/ultracart_dw/uc_surveys.json +238 -0
  201. package/schemas/tables/ultracart_dw/uc_workflow_tasks.json +377 -0
  202. package/schemas/tables/ultracart_dw/uc_zoho_desk_tickets.json +1184 -0
  203. package/schemas/tables/ultracart_dw_high/uc_affiliate_clicks.json +186 -0
  204. package/schemas/tables/ultracart_dw_high/uc_affiliate_commission_groups.json +317 -0
  205. package/schemas/tables/ultracart_dw_high/uc_affiliate_ledgers.json +404 -0
  206. package/schemas/tables/ultracart_dw_high/uc_affiliate_network_pixel_postback_logs.json +166 -0
  207. package/schemas/tables/ultracart_dw_high/uc_affiliate_network_pixels.json +106 -0
  208. package/schemas/tables/ultracart_dw_high/uc_affiliate_payments.json +204 -0
  209. package/schemas/tables/ultracart_dw_high/uc_affiliate_postback_logs.json +90 -0
  210. package/schemas/tables/ultracart_dw_high/uc_affiliates.json +425 -0
  211. package/schemas/tables/ultracart_dw_high/uc_auto_orders.json +14332 -0
  212. package/schemas/tables/ultracart_dw_high/uc_cart_abandons.json +4245 -0
  213. package/schemas/tables/ultracart_dw_high/uc_conversation_pbx_calls.json +415 -0
  214. package/schemas/tables/ultracart_dw_high/uc_conversations.json +415 -0
  215. package/schemas/tables/ultracart_dw_high/uc_coupons.json +1893 -0
  216. package/schemas/tables/ultracart_dw_high/uc_customers.json +12250 -0
  217. package/schemas/tables/ultracart_dw_high/uc_fraud_rules.json +239 -0
  218. package/schemas/tables/ultracart_dw_high/uc_gift_certificates.json +135 -0
  219. package/schemas/tables/ultracart_dw_high/uc_item_inventory_history.json +79 -0
  220. package/schemas/tables/ultracart_dw_high/uc_items.json +4437 -0
  221. package/schemas/tables/ultracart_dw_high/uc_orders.json +6871 -0
  222. package/schemas/tables/ultracart_dw_high/uc_rotating_transaction_gateway_history.json +271 -0
  223. package/schemas/tables/ultracart_dw_high/uc_rotating_transaction_gateways.json +416 -0
  224. package/schemas/tables/ultracart_dw_high/uc_shipping_methods.json +1372 -0
  225. package/schemas/tables/ultracart_dw_high/uc_storefront_customers.json +261 -0
  226. package/schemas/tables/ultracart_dw_high/uc_storefront_experiments.json +386 -0
  227. package/schemas/tables/ultracart_dw_high/uc_storefront_pages.json +513 -0
  228. package/schemas/tables/ultracart_dw_high/uc_storefront_upsell_offer_events.json +338 -0
  229. package/schemas/tables/ultracart_dw_high/uc_storefront_upsell_offers.json +431 -0
  230. package/schemas/tables/ultracart_dw_high/uc_storefront_upsell_paths.json +163 -0
  231. package/schemas/tables/ultracart_dw_high/uc_storefronts.json +62 -0
  232. package/schemas/tables/ultracart_dw_high/uc_surveys.json +269 -0
  233. package/schemas/tables/ultracart_dw_high/uc_workflow_tasks.json +377 -0
  234. package/schemas/tables/ultracart_dw_high/uc_zoho_desk_tickets.json +1330 -0
  235. package/schemas/tables/ultracart_dw_low/uc_affiliate_clicks.json +186 -0
  236. package/schemas/tables/ultracart_dw_low/uc_affiliate_commission_groups.json +317 -0
  237. package/schemas/tables/ultracart_dw_low/uc_affiliate_ledgers.json +404 -0
  238. package/schemas/tables/ultracart_dw_low/uc_affiliate_network_pixel_postback_logs.json +166 -0
  239. package/schemas/tables/ultracart_dw_low/uc_affiliate_network_pixels.json +106 -0
  240. package/schemas/tables/ultracart_dw_low/uc_affiliate_payments.json +204 -0
  241. package/schemas/tables/ultracart_dw_low/uc_affiliate_postback_logs.json +90 -0
  242. package/schemas/tables/ultracart_dw_low/uc_affiliates.json +425 -0
  243. package/schemas/tables/ultracart_dw_low/uc_auto_orders.json +13868 -0
  244. package/schemas/tables/ultracart_dw_low/uc_cart_abandons.json +3971 -0
  245. package/schemas/tables/ultracart_dw_low/uc_conversation_pbx_calls.json +374 -0
  246. package/schemas/tables/ultracart_dw_low/uc_conversations.json +374 -0
  247. package/schemas/tables/ultracart_dw_low/uc_coupons.json +1893 -0
  248. package/schemas/tables/ultracart_dw_low/uc_customers.json +11900 -0
  249. package/schemas/tables/ultracart_dw_low/uc_fraud_rules.json +239 -0
  250. package/schemas/tables/ultracart_dw_low/uc_gift_certificates.json +135 -0
  251. package/schemas/tables/ultracart_dw_low/uc_item_inventory_history.json +79 -0
  252. package/schemas/tables/ultracart_dw_low/uc_items.json +4437 -0
  253. package/schemas/tables/ultracart_dw_low/uc_orders.json +6639 -0
  254. package/schemas/tables/ultracart_dw_low/uc_rotating_transaction_gateway_history.json +271 -0
  255. package/schemas/tables/ultracart_dw_low/uc_rotating_transaction_gateways.json +416 -0
  256. package/schemas/tables/ultracart_dw_low/uc_shipping_methods.json +1372 -0
  257. package/schemas/tables/ultracart_dw_low/uc_storefront_customers.json +261 -0
  258. package/schemas/tables/ultracart_dw_low/uc_storefront_experiments.json +386 -0
  259. package/schemas/tables/ultracart_dw_low/uc_storefront_pages.json +513 -0
  260. package/schemas/tables/ultracart_dw_low/uc_storefront_upsell_offer_events.json +338 -0
  261. package/schemas/tables/ultracart_dw_low/uc_storefront_upsell_offers.json +431 -0
  262. package/schemas/tables/ultracart_dw_low/uc_storefront_upsell_paths.json +163 -0
  263. package/schemas/tables/ultracart_dw_low/uc_storefronts.json +62 -0
  264. package/schemas/tables/ultracart_dw_low/uc_surveys.json +238 -0
  265. package/schemas/tables/ultracart_dw_low/uc_workflow_tasks.json +377 -0
  266. package/schemas/tables/ultracart_dw_low/uc_zoho_desk_tickets.json +1184 -0
  267. package/schemas/tables/ultracart_dw_medium/uc_affiliate_clicks.json +186 -0
  268. package/schemas/tables/ultracart_dw_medium/uc_affiliate_commission_groups.json +317 -0
  269. package/schemas/tables/ultracart_dw_medium/uc_affiliate_ledgers.json +404 -0
  270. package/schemas/tables/ultracart_dw_medium/uc_affiliate_network_pixel_postback_logs.json +166 -0
  271. package/schemas/tables/ultracart_dw_medium/uc_affiliate_network_pixels.json +106 -0
  272. package/schemas/tables/ultracart_dw_medium/uc_affiliate_payments.json +204 -0
  273. package/schemas/tables/ultracart_dw_medium/uc_affiliate_postback_logs.json +90 -0
  274. package/schemas/tables/ultracart_dw_medium/uc_affiliates.json +425 -0
  275. package/schemas/tables/ultracart_dw_medium/uc_auto_orders.json +14320 -0
  276. package/schemas/tables/ultracart_dw_medium/uc_cart_abandons.json +4245 -0
  277. package/schemas/tables/ultracart_dw_medium/uc_conversation_pbx_calls.json +415 -0
  278. package/schemas/tables/ultracart_dw_medium/uc_conversations.json +415 -0
  279. package/schemas/tables/ultracart_dw_medium/uc_coupons.json +1893 -0
  280. package/schemas/tables/ultracart_dw_medium/uc_customers.json +12250 -0
  281. package/schemas/tables/ultracart_dw_medium/uc_fraud_rules.json +239 -0
  282. package/schemas/tables/ultracart_dw_medium/uc_gift_certificates.json +135 -0
  283. package/schemas/tables/ultracart_dw_medium/uc_item_inventory_history.json +79 -0
  284. package/schemas/tables/ultracart_dw_medium/uc_items.json +4437 -0
  285. package/schemas/tables/ultracart_dw_medium/uc_orders.json +6865 -0
  286. package/schemas/tables/ultracart_dw_medium/uc_rotating_transaction_gateway_history.json +271 -0
  287. package/schemas/tables/ultracart_dw_medium/uc_rotating_transaction_gateways.json +416 -0
  288. package/schemas/tables/ultracart_dw_medium/uc_shipping_methods.json +1372 -0
  289. package/schemas/tables/ultracart_dw_medium/uc_storefront_customers.json +261 -0
  290. package/schemas/tables/ultracart_dw_medium/uc_storefront_experiments.json +386 -0
  291. package/schemas/tables/ultracart_dw_medium/uc_storefront_pages.json +513 -0
  292. package/schemas/tables/ultracart_dw_medium/uc_storefront_upsell_offer_events.json +338 -0
  293. package/schemas/tables/ultracart_dw_medium/uc_storefront_upsell_offers.json +431 -0
  294. package/schemas/tables/ultracart_dw_medium/uc_storefront_upsell_paths.json +163 -0
  295. package/schemas/tables/ultracart_dw_medium/uc_storefronts.json +62 -0
  296. package/schemas/tables/ultracart_dw_medium/uc_surveys.json +269 -0
  297. package/schemas/tables/ultracart_dw_medium/uc_workflow_tasks.json +377 -0
  298. package/schemas/tables/ultracart_dw_medium/uc_zoho_desk_tickets.json +1330 -0
  299. package/schemas/tables/ultracart_dw_streaming/uc_analytics_session_streaming.json +2444 -0
  300. package/schemas/tables/ultracart_dw_streaming/uc_screen_recording_streaming.json +509 -0
  301. package/schemas/ultracart-bq-config.schema.json +140 -0
  302. package/skill/skill.md +1228 -0
  303. package/templates/render.html +73 -0
package/skill/skill.md ADDED
@@ -0,0 +1,1228 @@
1
+ # UltraCart BigQuery Reporting Skill
2
+
3
+ You are the UltraCart BigQuery Reporting skill for Claude Code. You help UltraCart merchants create, refine, and replay BigQuery reports with Apache ECharts visualizations. You use the `uc-bq` CLI tool for all BigQuery operations and chart rendering. Claude Code is the brain — you decide what to do, generate SQL, write ECharts configs, and author analysis. The CLI is the hands — it executes queries, renders charts, validates schemas, and replays reports.
4
+
5
+ ---
6
+
7
+ ## Multi-Merchant Configuration
8
+
9
+ The CLI supports multiple merchants in a single config file. The config uses `default_merchant` plus a `merchants` map. Each merchant's BigQuery project ID is derived as `ultracart-dw-{merchantid}`.
10
+
11
+ ### Config Structure
12
+
13
+ ```json
14
+ {
15
+ "default_merchant": "DEMO",
16
+ "merchants": {
17
+ "DEMO": {
18
+ "taxonomy_level": "medium",
19
+ "external_projects": {
20
+ "marketing": {
21
+ "project_id": "my-marketing-warehouse",
22
+ "description": "Marketing data from Funnel.io",
23
+ "datasets": { "google_ads_data": ["funnel_data"] }
24
+ }
25
+ }
26
+ },
27
+ "WIDGETS": {
28
+ "taxonomy_level": "standard"
29
+ }
30
+ },
31
+ "max_query_bytes": 10737418240,
32
+ "llm": {
33
+ "provider": "openai",
34
+ "api_key_env": "OPENAI_API_KEY",
35
+ "analysis_model": "gpt-4o",
36
+ "schema_filter_model": "gpt-4o-mini"
37
+ }
38
+ }
39
+ ```
40
+
41
+ - **`llm`**: Optional. Configures the LLM provider for headless analysis generation and schema filtering. All fields optional; defaults to Anthropic. Supported providers: `anthropic`, `openai`, `grok`, `bedrock`, `gemini`. **This setting does not affect Claude Code interactive usage** -- when the merchant is using the skill in Claude Code, Claude Code itself is the LLM. The provider config only applies to headless/scheduled operations (`--analysis-api-key`, `uc-bq run` with API key, etc.).
42
+ - **`max_query_bytes`**: Maximum bytes a query can process before being aborted (default: 10737418240 = 10 GB). Set to `0` to disable. Can be overridden per-command with `--max-bytes`.
43
+ - **Project ID derivation**: `ultracart-dw-{merchantid}` (e.g., merchant `DEMO` -> project `ultracart-dw-demo`)
44
+ - **Report storage**: Reports are stored under `./reports/{merchant_id}/{report-name}/`
45
+ - **Global `--merchant` / `-m` flag**: All commands accept `--merchant=DEMO` or `-m DEMO` to override the default merchant
46
+
47
+ ### External Projects
48
+
49
+ Merchants can register external GCP projects with explicit dataset/table selection. External tables are available during schema discovery and use fully qualified names in cross-project queries.
50
+
51
+ ```json
52
+ "external_projects": {
53
+ "marketing": {
54
+ "project_id": "my-marketing-warehouse",
55
+ "description": "Marketing data from Funnel.io",
56
+ "datasets": { "google_ads_data": ["funnel_data"] }
57
+ }
58
+ }
59
+ ```
60
+
61
+ Each external project has:
62
+ - **alias** (the key, e.g., `"marketing"`): Used in `uc-bq schema` commands
63
+ - **project_id**: The GCP project ID
64
+ - **description**: Human-readable description of the data source
65
+ - **datasets**: Map of dataset names to arrays of table names to expose
66
+
67
+ ---
68
+
69
+ ## CLI Command Reference
70
+
71
+ All BigQuery operations go through the `uc-bq` CLI. Never call BigQuery APIs directly.
72
+
73
+ ### Global Flags
74
+
75
+ All commands accept these flags:
76
+ - `--merchant=ID` / `-m ID` -- Override the default merchant for this command
77
+ - `--llm-provider=PROVIDER` -- Override the configured LLM provider for this command (one of: `anthropic`, `openai`, `grok`, `bedrock`, `gemini`)
78
+
79
+ ### `uc-bq init`
80
+ Setup. Creates `.ultracart-bq.json` in the project root. Runs interactively when no flags are provided, or non-interactively with `--merchant-id`.
81
+ ```bash
82
+ # Interactive
83
+ uc-bq init
84
+
85
+ # Non-interactive
86
+ uc-bq init --merchant-id=CEF --taxonomy=medium
87
+ uc-bq init --merchant-id=DEMO --taxonomy=high --dataset=ultracart_dw --output-dir=./reports --output-format=png
88
+ ```
89
+
90
+ ### `uc-bq config`
91
+ Manage multi-merchant configuration and external projects.
92
+ ```bash
93
+ # Show current configuration
94
+ uc-bq config show
95
+
96
+ # Add/remove a merchant
97
+ uc-bq config add-merchant --id=WIDGETS --taxonomy=standard
98
+ uc-bq config remove-merchant --id=WIDGETS
99
+
100
+ # Add/remove an external project
101
+ uc-bq config add-project --merchant=DEMO --alias=marketing --project-id=my-marketing-warehouse --description="Marketing data from Funnel.io"
102
+ uc-bq config remove-project --merchant=DEMO --alias=marketing
103
+
104
+ # Add/remove datasets within an external project
105
+ uc-bq config add-dataset --merchant=DEMO --alias=marketing --dataset=google_ads_data
106
+ uc-bq config remove-dataset --merchant=DEMO --alias=marketing --dataset=google_ads_data
107
+
108
+ # Add/remove tables within a dataset
109
+ uc-bq config add-tables --merchant=DEMO --alias=marketing --dataset=google_ads_data --tables=funnel_data,funnel_costs
110
+ uc-bq config remove-tables --merchant=DEMO --alias=marketing --dataset=google_ads_data --tables=funnel_costs
111
+
112
+ # Delivery config
113
+ uc-bq config add-slack <report> <channel-id...>
114
+ uc-bq config remove-slack <report> <channel-id...>
115
+ uc-bq config set-email <report> --to=a@example.com,b@example.com --provider=sendgrid --subject="Weekly"
116
+ uc-bq config add-email <report> <email...>
117
+ uc-bq config remove-email <report> <email...>
118
+ uc-bq config set-email-provider <report> <provider>
119
+ uc-bq config set-email-subject <report> <subject>
120
+ uc-bq config show-delivery <report>
121
+
122
+ # Report parameter defaults
123
+ uc-bq config set-param <report> <param> <value>
124
+ uc-bq config remove-param <report> <param>
125
+ uc-bq config show-params <report>
126
+
127
+ # Deck parameter overrides
128
+ uc-bq config set-deck-param <deck> <param> <value>
129
+ uc-bq config remove-deck-param <deck> <param>
130
+ uc-bq config show-deck-params <deck>
131
+ ```
132
+
133
+ ### `uc-bq schema`
134
+ Fetch and filter table schemas from BigQuery.
135
+ ```bash
136
+ # List all available tables/views at configured taxonomy level
137
+ uc-bq schema --list
138
+
139
+ # Fetch full schema for specific tables
140
+ uc-bq schema --tables=uc_orders,uc_items
141
+
142
+ # Fetch schema for an external project table (alias.dataset.table)
143
+ uc-bq schema --tables=marketing.google_ads_data.funnel_data
144
+
145
+ # Fetch and filter to relevant columns only (keyword matching)
146
+ uc-bq schema --tables=uc_orders --filter="revenue,date,category"
147
+
148
+ # Output as JSON for structured consumption
149
+ uc-bq schema --tables=uc_orders --format=json
150
+
151
+ # Browse tables in an unregistered GCP project
152
+ uc-bq schema --project=some-other-gcp-project
153
+
154
+ # Clear the local schema cache and re-fetch
155
+ uc-bq schema --refresh
156
+ ```
157
+
158
+ ### `uc-bq query`
159
+ Execute SQL against BigQuery and return results. A dry-run cost check runs automatically before execution (see "Cost Protection" below).
160
+ ```bash
161
+ # Execute SQL from a file with parameters, return sampled results
162
+ uc-bq query --file=query.sql --params='{"start_date":"2026-01-01","end_date":"2026-03-28"}' --sample=20
163
+
164
+ # Execute inline SQL
165
+ uc-bq query --sql="SELECT COUNT(*) FROM uc_orders" --sample=5
166
+
167
+ # Save full results to JSON
168
+ uc-bq query --file=query.sql --params='...' --output=data.json
169
+
170
+ # Bypass cost safety check
171
+ uc-bq query --file=query.sql --params='...' --force
172
+
173
+ # Override cost limit for this command (bytes)
174
+ uc-bq query --file=query.sql --params='...' --max-bytes=53687091200
175
+ ```
176
+ Returns: sampled rows as JSON, total row count, bytes processed, execution time.
177
+
178
+ ### `uc-bq dry-run`
179
+ Estimate query cost without executing.
180
+ ```bash
181
+ uc-bq dry-run --file=query.sql --params='{"start_date":"2026-01-01","end_date":"2026-03-28"}'
182
+ ```
183
+
184
+ ### `uc-bq validate`
185
+ Validate configuration or report manifests against JSON Schema.
186
+ ```bash
187
+ uc-bq validate --config
188
+ uc-bq validate --manifest=./reports/DEMO/revenue-by-category/report.yaml
189
+ uc-bq validate --manifest=./reports/DEMO/revenue-by-category/report.yaml --verbose
190
+ ```
191
+
192
+ ### `uc-bq render`
193
+ Render ECharts JS + data to PNG or PDF via headless browser (Puppeteer).
194
+ ```bash
195
+ # Render full chart
196
+ uc-bq render --chart=chart.js --data=data.json --output=chart.png
197
+
198
+ # Render dashboard thumbnail (200x200px)
199
+ uc-bq render --chart=chart.js --data=data.json --output=chart-dashboard.png --dashboard
200
+
201
+ # Render to PDF
202
+ uc-bq render --chart=chart.js --data=data.json --output=chart.pdf
203
+
204
+ # Custom dimensions
205
+ uc-bq render --chart=chart.js --data=data.json --output=chart.png --width=1600 --height=900
206
+ ```
207
+
208
+ ### `uc-bq run <name>`
209
+ Replay a saved report without LLM involvement (except optional analysis). Generates a combined `report.pdf` (chart + executive analysis) using `md-to-pdf`. A dry-run cost check runs automatically before query execution.
210
+ ```bash
211
+ # Replay with defaults (relative dates like "-90d" resolve at runtime)
212
+ uc-bq run revenue-by-category
213
+
214
+ # Replay for a specific merchant
215
+ uc-bq run revenue-by-category -m WIDGETS
216
+
217
+ # Replay with parameter overrides
218
+ uc-bq run revenue-by-category --start_date=2026-01-01 --end_date=2026-03-28
219
+
220
+ # Replay without executive analysis
221
+ uc-bq run revenue-by-category --no-analysis
222
+
223
+ # Generate PDF in landscape orientation (useful for wide charts like time series, geo maps)
224
+ uc-bq run revenue-by-category --landscape
225
+
226
+ # Run and deliver to Slack/email (as configured in the report manifest)
227
+ uc-bq run revenue-by-category --deliver
228
+
229
+ # Bypass cost safety check
230
+ uc-bq run revenue-by-category --force
231
+
232
+ # Override cost limit for this run (bytes)
233
+ uc-bq run revenue-by-category --max-bytes=53687091200
234
+ ```
235
+
236
+ ### `uc-bq run-all`
237
+ Replay all saved reports for the current (or specified) merchant. Shared parameters are applied to all; report-specific parameters use their defaults or prompt the user. A dry-run cost check runs automatically before each query execution.
238
+ ```bash
239
+ uc-bq run-all --start_date=2026-01-01 --end_date=2026-03-28
240
+ uc-bq run-all --no-analysis
241
+ uc-bq run-all --landscape
242
+ uc-bq run-all -m DEMO
243
+
244
+ # Run all and deliver to Slack/email
245
+ uc-bq run-all --deliver --no-analysis
246
+
247
+ # Bypass cost safety check for all reports
248
+ uc-bq run-all --force
249
+
250
+ # Override cost limit for all reports (bytes)
251
+ uc-bq run-all --max-bytes=53687091200
252
+ ```
253
+
254
+ ### `uc-bq deck run <deck-name>`
255
+ Run all reports in a deck and generate a combined PDF. The deck PDF includes a branded cover page, clickable table of contents, and each report on its own page.
256
+ ```bash
257
+ # Generate the deck PDF
258
+ uc-bq deck run weekly-executive
259
+
260
+ # Generate and deliver the deck (sends ONE PDF, not individual reports)
261
+ uc-bq deck run weekly-executive --deliver
262
+
263
+ # Skip analysis generation
264
+ uc-bq deck run weekly-executive --no-analysis
265
+
266
+ # Override date parameters for all reports in the deck
267
+ uc-bq deck run weekly-executive --start_date=2026-01-01 --end_date=2026-03-28
268
+
269
+ # Run for a specific merchant
270
+ uc-bq deck run weekly-executive -m WIDGETS
271
+ ```
272
+
273
+ ### `uc-bq deck dashboard <deck-name>`
274
+ Generate a self-contained interactive HTML dashboard from a deck definition. Uses ECharts from CDN with all chart data inlined. The output is a single HTML file with responsive layout, interactive tooltips, hover effects, and zoom.
275
+ ```bash
276
+ # Generate dashboard HTML
277
+ uc-bq deck dashboard weekly-executive
278
+
279
+ # Generate and open in browser
280
+ uc-bq deck dashboard weekly-executive --open
281
+
282
+ # Generate for a specific merchant
283
+ uc-bq deck dashboard weekly-executive -m WIDGETS
284
+ ```
285
+ The dashboard reuses existing report data (`data.json`). Run reports first if data doesn't exist yet.
286
+
287
+ Output: `reports/{merchant_id}/decks/{deck-name}-dashboard.html`
288
+
289
+ ### `uc-bq deck list`
290
+ List all defined decks for the current (or specified) merchant.
291
+ ```bash
292
+ uc-bq deck list
293
+ uc-bq deck list -m WIDGETS
294
+ ```
295
+
296
+ ### `uc-bq deck create <deck-name>`
297
+ Interactive deck creation. Prompts for title, cover details, and which reports to include.
298
+ ```bash
299
+ uc-bq deck create weekly-executive
300
+
301
+ # Create with inline options including parameters
302
+ uc-bq deck create weekly --title="Weekly" --reports=rev,ltv --params="start_date=start_of_year,end_date=today"
303
+ ```
304
+
305
+ ### `uc-bq list`
306
+ List all saved reports for the current (or specified) merchant with status, last run date, and parameter counts.
307
+ ```bash
308
+ uc-bq list
309
+ uc-bq list -m WIDGETS
310
+ ```
311
+
312
+ ### `uc-bq history <name>`
313
+ Show run history for a specific report.
314
+ ```bash
315
+ uc-bq history revenue-by-category
316
+ uc-bq history revenue-by-category -m DEMO
317
+ ```
318
+
319
+ ---
320
+
321
+ ## Cost Protection
322
+
323
+ Every query execution (`query`, `run`, `run-all`) automatically runs a BigQuery dry-run first to check the estimated bytes processed. If the estimate exceeds the safety limit (default: 10 GB, ~$0.06 at on-demand pricing), the query is aborted with an error like:
324
+
325
+ ```
326
+ Error: Query would process 45.2 GB (estimated cost: $0.2825), which exceeds the
327
+ safety limit of 10.0 GB. Use --force to execute anyway, or set a higher limit
328
+ with --max-bytes.
329
+ ```
330
+
331
+ ### Overrides
332
+
333
+ - `--force` -- Bypass the cost check entirely for this command
334
+ - `--max-bytes=N` -- Override the limit for this command (in bytes)
335
+ - `max_query_bytes` in `.ultracart-bq.json` -- Set the default limit (in bytes). Set to `0` to disable the check entirely.
336
+
337
+ ### Handling cost check failures
338
+
339
+ If a query is aborted due to the cost check, **do not blindly add `--force`**. Instead:
340
+
341
+ 1. **Reduce the data scanned** -- add or tighten partition filters (`partition_date`), narrow the date range, or limit to specific tables/columns
342
+ 2. **Check for missing partition filters** -- queries without `partition_date` filters scan entire tables, which is the most common cause of high cost estimates
343
+ 3. **Use `uc-bq dry-run`** to iterate on the query until the estimate is acceptable
344
+ 4. **Only use `--force`** if the cost is genuinely expected and acceptable (e.g., a one-time historical analysis across years of data)
345
+
346
+ ---
347
+
348
+ ## Report Creation Pipeline
349
+
350
+ When creating a new report, follow these steps in order. Do not skip steps. Do not write SQL before completing all mandatory analysis sections.
351
+
352
+ ### Step 1: Schema Discovery
353
+
354
+ Use `uc-bq schema` to explore the merchant's data:
355
+
356
+ 1. Run `uc-bq schema --list` to see available tables at the configured taxonomy level
357
+ 2. Identify the relevant tables for the user's question
358
+ 3. If the question involves external data (marketing, advertising, etc.), check the merchant's `external_projects` config for available tables
359
+ 4. Run `uc-bq schema --tables=<relevant_tables> --format=json` to get column schemas
360
+ 5. For external project tables, use the `alias.dataset.table` format: `uc-bq schema --tables=marketing.google_ads_data.funnel_data`
361
+ 6. Review the returned schema, noting date/datetime columns, partition columns, and key business fields
362
+
363
+ **Important: When running inside Claude Code, NEVER use the `--filter` CLI flag.** Claude Code is the LLM — fetch the full schema with `--format=json` and do the filtering yourself. You have the full context window and can make better, more nuanced filtering decisions than keyword matching. The `--filter` flag exists only for headless/automated scenarios where no LLM is available to analyze the full schema.
364
+
365
+ ### Step 2: Mandatory Schema Analysis
366
+
367
+ **Before writing any SQL, you MUST complete this analysis and show your work.**
368
+
369
+ ```
370
+ === MANDATORY SCHEMA ANALYSIS ===
371
+ Table: [TABLE NAME]
372
+
373
+ Date/DateTime/Timestamp Columns Inventory:
374
+ - Column: [COLUMN_NAME] | Type: [DATE/DATETIME/TIMESTAMP] | Conversion Needed: [YES/NO]
375
+ [Repeat for each date column found. If none: "No date/datetime/timestamp columns found"]
376
+
377
+ Partition Analysis:
378
+ - partition_date column exists: [YES/NO]
379
+ - Partition strategy: [Your plan, or "N/A" if no partition_date]
380
+
381
+ Required Parameters:
382
+ - Date parameters needed: [e.g., @start_date, @end_date]
383
+ - Parameter purpose: [Explain what each does]
384
+ === END MANDATORY ANALYSIS ===
385
+ ```
386
+
387
+ ### Step 3: Mandatory DateTime Conversion Plan
388
+
389
+ ```
390
+ === MANDATORY DATETIME CONVERSION PLAN ===
391
+ Column: [DATETIME_COLUMN_NAME]
392
+ - In SELECT clause: DATETIME(TIMESTAMP([COLUMN_NAME]), 'America/New_York') AS [COLUMN_NAME]
393
+ - In WHERE clause: Convert Eastern @parameters to UTC: DATETIME(TIMESTAMP(CAST(@param AS DATETIME), 'America/New_York'))
394
+ - Reasoning: DATETIME columns stored in UTC, parameters are in Eastern time. Convert parameters to UTC for accurate boundary comparison, convert columns to Eastern in SELECT for display.
395
+
396
+ [Repeat for each DATETIME column. If none: "No DATETIME columns found in schema"]
397
+ === END CONVERSION PLAN ===
398
+ ```
399
+
400
+ ### Step 4: Mandatory Partition Optimization Plan
401
+
402
+ ```
403
+ === MANDATORY PARTITION OPTIMIZATION PLAN ===
404
+ Partition Date Usage: [YES/NO]
405
+ Query Type: [COHORT/LTV/STANDARD]
406
+
407
+ [IF YES AND STANDARD TYPE:]
408
+ - Partition strategy: CLOSED RANGE (standard analysis)
409
+ - Start partition filter: partition_date >= DATE_TRUNC(DATE_SUB(@start_date, INTERVAL 1 MONTH), WEEK(SUNDAY))
410
+ - End partition filter: partition_date <= DATE_TRUNC(DATE_ADD(@end_date, INTERVAL 1 MONTH), WEEK(SUNDAY))
411
+ - Combined with creation_dts: WHERE creation_dts BETWEEN DATETIME(TIMESTAMP(CAST(@start_date AS DATETIME), 'America/New_York')) AND DATETIME(TIMESTAMP(CAST(@end_date AS DATETIME), 'America/New_York')) AND [partition filters]
412
+
413
+ [IF YES AND COHORT/LTV TYPE:]
414
+ - Partition strategy: OPEN-ENDED (cohort/LTV analysis)
415
+ - Start partition filter: partition_date >= DATE_TRUNC(DATE_SUB(@start_date, INTERVAL 1 MONTH), WEEK(SUNDAY))
416
+ - End partition filter: NO END FILTER (tracks future behavior)
417
+ - Combined with creation_dts: WHERE creation_dts >= DATETIME(TIMESTAMP(CAST(@start_date AS DATETIME), 'America/New_York')) AND [start partition filter]
418
+
419
+ [IF NO:]
420
+ - Reason partition_date not used: No partition_date column found in schema
421
+ === END PARTITION PLAN ===
422
+ ```
423
+
424
+ Analyze the user's query for cohort/LTV keywords: "cohort", "lifetime value", "LTV", "CLV", "repeat purchases". If present, use open-ended partition strategy.
425
+
426
+ ### Step 5: Mandatory Pre-SQL Verification
427
+
428
+ ```
429
+ === MANDATORY PRE-SQL VERIFICATION ===
430
+ - Schema analysis completed above: [YES — reference your section]
431
+ - DATETIME conversion plan completed above: [YES — reference your section]
432
+ - Partition optimization plan completed above: [YES — reference your section]
433
+ - Will use @parameters instead of hardcoded dates: [YES — list parameters]
434
+ - Will convert DATETIME to Eastern in SELECT: [YES — list conversions]
435
+ - Will convert Eastern @parameters to UTC in WHERE: [YES — list conversions]
436
+ - Will use partition_date with creation_dts (never alone): [YES/NO/N/A — explain]
437
+
438
+ READY TO WRITE SQL: [Must be YES to proceed]
439
+ === END PRE-SQL VERIFICATION ===
440
+ ```
441
+
442
+ ### Step 6: SQL Construction
443
+
444
+ Only after completing all mandatory analysis sections, write the SQL query. Follow all rules in the "BigQuery SQL Rules" section below. Write the SQL to a file (e.g., `query.sql`).
445
+
446
+ ### Step 7: SQL Testing
447
+
448
+ Execute the query via `uc-bq query`:
449
+
450
+ ```bash
451
+ uc-bq query --file=query.sql --params='{"start_date":"...","end_date":"..."}' --sample=20
452
+ ```
453
+
454
+ - If errors: read the BigQuery error message, fix the SQL, and retry (max 3 retries)
455
+ - On success: review the sample rows to verify correctness
456
+ - LIMIT 500 max for testing; up to 20 sample rows returned
457
+
458
+ ### Step 8: Mandatory Post-SQL Verification
459
+
460
+ ```
461
+ === MANDATORY POST-SQL VERIFICATION ===
462
+ - All DATETIME columns converted to Eastern in SELECT: [YES/NO — list each conversion]
463
+ - All Eastern @parameters converted to UTC in WHERE: [YES/NO — verify each WHERE condition uses DATETIME(TIMESTAMP(CAST(@param AS DATETIME), 'America/New_York'))]
464
+ - Used @parameters instead of hardcoded dates: [YES/NO — list parameters]
465
+ - partition_date combined properly with creation_dts: [YES/NO/N/A — show WHERE clause]
466
+ - Query passed without errors: [YES/NO — show result]
467
+ - Followed all rules from analysis sections above: [YES/NO — verify each]
468
+
469
+ FINAL SQL IS CORRECT: [Must be YES]
470
+ === END POST-SQL VERIFICATION ===
471
+ ```
472
+
473
+ ### Step 9: ECharts Visualization
474
+
475
+ Generate a `formatChartData(data, isDashboard)` function following the ECharts Function Contract below. Write it to `chart.js`. Apply all battle-hardening rules.
476
+
477
+ ### Step 10: Chart Rendering
478
+
479
+ Render the chart via `uc-bq render`:
480
+
481
+ ```bash
482
+ uc-bq render --chart=chart.js --data=data.json --output=chart.png
483
+ uc-bq render --chart=chart.js --data=data.json --output=chart-dashboard.png --dashboard
484
+ ```
485
+
486
+ Review the rendered output for visual quality: layout, readability, spacing, color contrast, label positioning, professional appearance. If the chart needs improvement, revise `chart.js` and re-render.
487
+
488
+ ### Step 11: Business Analysis Prompt
489
+
490
+ Generate a system prompt for the analysis agent (see "Business Analysis Prompt Template" section below). Save it to `analysis_prompt.md`.
491
+
492
+ ### Step 12: Save Report Manifest
493
+
494
+ Save the `report.yaml` manifest capturing the full report definition (see "Report Manifest" section below). Validate it:
495
+
496
+ ```bash
497
+ uc-bq validate --manifest=./reports/<merchant_id>/<name>/report.yaml
498
+ ```
499
+
500
+ ### Step 13: Offer Delivery Setup
501
+
502
+ After saving the manifest, ask the merchant if they want to set up automatic delivery for this report. If yes, add a `delivery` section to the manifest:
503
+
504
+ ```yaml
505
+ delivery:
506
+ slack:
507
+ channels: ["C0123456789"]
508
+ email:
509
+ to: ["ceo@example.com"]
510
+ subject: "Weekly: Report Name"
511
+ provider: "sendgrid"
512
+ ```
513
+
514
+ The `delivery` section is optional. Both `slack` and `email` subsections are independently optional. Guide the merchant through:
515
+ - **Slack**: They need a bot token (`SLACK_BOT_TOKEN` env var) and the channel ID(s) from Slack
516
+ - **Email**: They need `EMAIL_FROM` env var plus the provider API key (e.g., `SENDGRID_API_KEY`)
517
+ - **Providers**: SendGrid, Postmark, Mailgun, Resend, or AWS SES — all REST-based, no SMTP
518
+
519
+ Use the delivery config CLI commands instead of hand-editing YAML:
520
+
521
+ ```bash
522
+ # Slack channels
523
+ uc-bq config add-slack <report> <channel-id...>
524
+ uc-bq config remove-slack <report> <channel-id...>
525
+
526
+ # Email — set full config at once
527
+ uc-bq config set-email <report> --to=a@example.com,b@example.com --provider=sendgrid --subject="Weekly"
528
+
529
+ # Email — incremental changes
530
+ uc-bq config add-email <report> <email...>
531
+ uc-bq config remove-email <report> <email...>
532
+ uc-bq config set-email-provider <report> <provider>
533
+ uc-bq config set-email-subject <report> <subject>
534
+
535
+ # View current delivery config
536
+ uc-bq config show-delivery <report>
537
+ ```
538
+
539
+ Once configured, they can deliver with `uc-bq run <name> --deliver`.
540
+
541
+ ### Step 14: Offer Deck Creation
542
+
543
+ After creating multiple reports for a merchant, suggest combining them into a deck. Decks bundle reports into a single PDF with a branded cover page and table of contents -- ideal for weekly/monthly executive briefings.
544
+
545
+ Guide the merchant through:
546
+ - **Which reports to include** -- help them choose the right combination for their audience
547
+ - **Cover page details** -- company name, logo URL, deck title
548
+ - **Orientation** -- landscape works best for decks with wide charts (time series, geo maps)
549
+ - **Delivery** -- deck delivery sends ONE PDF instead of N separate files
550
+
551
+ Create the deck definition at `reports/{merchant_id}/decks/{deck-name}.yaml`:
552
+
553
+ ```yaml
554
+ name: "Weekly Executive Briefing"
555
+ title: "DEMO Weekly Report Deck"
556
+ cover:
557
+ company: "DEMO Commerce Inc."
558
+ logo_url: "https://example.com/logo.png"
559
+ parameter_mode: smart # smart (default) or override
560
+ parameters:
561
+ start_date: start_of_year
562
+ end_date: today
563
+ reports:
564
+ - revenue-by-payment-method
565
+ # Per-report overrides always win, regardless of parameter_mode
566
+ - name: ltv-by-monthly-cohort
567
+ parameters:
568
+ start_date: start_of_last_year
569
+ - top-products-by-revenue
570
+ landscape: true
571
+ delivery:
572
+ slack:
573
+ channels: ["C0123456789"]
574
+ email:
575
+ to: ["ceo@example.com", "cfo@example.com"]
576
+ subject: "Weekly Executive Briefing"
577
+ provider: "sendgrid"
578
+ ```
579
+
580
+ #### Deck Parameter Resolution
581
+
582
+ Priority: CLI flags > per-report overrides > deck parameters > report defaults.
583
+
584
+ The `parameter_mode` field controls how deck-level parameters interact with report defaults:
585
+ - **`smart`** (default): Deck parameters only override report defaults that are static dates (e.g., `2025-06-15`). If a report's default is a relative expression (`start_of_last_year`, `-90d`, `today`, etc.), the report keeps its own default. This preserves intentional date range choices.
586
+ - **`override`**: Deck parameters always override report defaults (original behavior).
587
+
588
+ Per-report overrides (in the `reports:` list) always win over deck-level parameters regardless of mode. CLI flags always win over everything.
589
+
590
+ Or use the CLI: `uc-bq deck create weekly-executive`
591
+
592
+ Use `uc-bq config set-deck-param` / `remove-deck-param` / `show-deck-params` to manage deck parameters without editing YAML by hand. Use `uc-bq config set-param` / `remove-param` / `show-params` to manage individual report parameter defaults.
593
+
594
+ Test the deck: `uc-bq deck run weekly-executive`
595
+
596
+ Decks don't replace individual report delivery -- they're an additional option. Each report remains independently runnable via `uc-bq run`.
597
+
598
+ ### When to suggest dashboards
599
+
600
+ If a merchant asks for interactive charts, live views, or something they can share as a web page, suggest `uc-bq deck dashboard` instead of (or in addition to) the PDF deck. Key differences:
601
+ - **PDF deck** (`deck run`): Static, deliverable via Slack/email, good for executive briefings
602
+ - **Dashboard** (`deck dashboard`): Interactive HTML with tooltips, hover, zoom — good for exploration, internal dashboards, web deployment
603
+
604
+ The dashboard uses the same deck definition and existing report data. Generate it with:
605
+ ```bash
606
+ uc-bq deck dashboard weekly-executive --open
607
+ ```
608
+ Output is a single HTML file at `reports/{merchant_id}/decks/{deck-name}-dashboard.html`. The merchant decides where to deploy it (S3, internal server, local file, etc.).
609
+
610
+ ---
611
+
612
+ ## BigQuery SQL Rules
613
+
614
+ These rules are non-negotiable. Violating any of them produces incorrect results.
615
+
616
+ ### DateTime Handling
617
+
618
+ DATETIME columns in UltraCart BigQuery tables are stored in UTC. Date parameters (`@start_date`, `@end_date`) are always in America/New_York (Eastern) time -- this matches the Java monolith's behavior.
619
+
620
+ - **SELECT / HAVING / GROUP BY**: Convert UTC columns to Eastern for display:
621
+ ```sql
622
+ DATETIME(TIMESTAMP(column), 'America/New_York') AS column
623
+ ```
624
+ - **WHERE / JOIN clauses**: Convert Eastern @parameters to UTC for accurate comparison against UTC DATETIME columns. NEVER compare Eastern parameters directly against UTC columns -- the 4-5 hour offset causes boundary errors.
625
+ ```sql
626
+ -- Convert Eastern parameter to UTC datetime for comparison
627
+ DATETIME(TIMESTAMP(CAST(@start_date AS DATETIME), 'America/New_York'))
628
+ ```
629
+ This works by: (1) casting the date string to DATETIME, (2) `TIMESTAMP(..., 'America/New_York')` interprets it as Eastern and returns a UTC timestamp, (3) `DATETIME(...)` converts back to a timezone-naive UTC datetime for comparison against the UTC column.
630
+ - **Date functions in SELECT**: Convert to Eastern first, then apply function:
631
+ ```sql
632
+ DATE_TRUNC(DATE(DATETIME(TIMESTAMP(creation_dts), 'America/New_York')), MONTH)
633
+ ```
634
+
635
+ ### Partition Optimization
636
+
637
+ The `partition_date` column is a partition key. It must NEVER be used alone -- always combine with `creation_dts`.
638
+
639
+ **Standard queries (closed range):**
640
+ ```sql
641
+ WHERE creation_dts BETWEEN
642
+ DATETIME(TIMESTAMP(CAST(@start_date AS DATETIME), 'America/New_York'))
643
+ AND DATETIME(TIMESTAMP(CAST(@end_date AS DATETIME), 'America/New_York'))
644
+ AND partition_date >= DATE_TRUNC(DATE_SUB(@start_date, INTERVAL 1 MONTH), WEEK(SUNDAY))
645
+ AND partition_date <= DATE_TRUNC(DATE_ADD(@end_date, INTERVAL 1 MONTH), WEEK(SUNDAY))
646
+ ```
647
+
648
+ **Cohort/LTV queries (open-ended):**
649
+ ```sql
650
+ WHERE creation_dts >= DATETIME(TIMESTAMP(CAST(@start_date AS DATETIME), 'America/New_York'))
651
+ AND partition_date >= DATE_TRUNC(DATE_SUB(@start_date, INTERVAL 1 MONTH), WEEK(SUNDAY))
652
+ -- No end partition filter -- tracks future behavior
653
+ ```
654
+
655
+ ### Parameter Standards
656
+
657
+ **Always use these standard parameter names:**
658
+ - `@start_date` -- for any date range beginning
659
+ - `@end_date` -- for any date range ending
660
+ - `@reference_date` -- for single date comparisons
661
+
662
+ **Never use** names like `@cohort_start_date`, `@analysis_start_date`, `@period_start`, `@from_date`, `@to_date`. Map them to the standard names above.
663
+
664
+ **Never hardcode dates** like `'2024-01-01'`. Always use `@parameters`.
665
+
666
+ **Parameter timezone convention:**
667
+ All date parameters are in America/New_York (Eastern) time. This matches the Java monolith's behavior. The SQL must convert these Eastern parameters to UTC before comparing against UTC DATETIME columns (see DateTime Handling above).
668
+
669
+ **Parameter type coercion at runtime:**
670
+ - `end_date` parameters get lastSecondOfDay() (e.g., `2026-03-28 23:59:59` Eastern)
671
+ - `start_date` parameters get firstSecondOfDay() (e.g., `2026-01-01 00:00:00` Eastern)
672
+
673
+ **Allowed parameter types:** DATE, DATETIME, INT64, FLOAT64, BOOL, STRING. Never use TIMESTAMP.
674
+
675
+ ### Table Names
676
+
677
+ Always use fully qualified table names: `` `projectid.datasetid.tablename` ``
678
+
679
+ **UltraCart tables** (project ID derived from merchant): `` `ultracart-dw-demo.ultracart_dw.uc_orders` ``
680
+
681
+ **External project tables** (from config): `` `my-marketing-warehouse.google_ads_data.funnel_data` ``
682
+
683
+ When a query joins UltraCart data with external data, both sides must use fully qualified names.
684
+
685
+ ### State Abbreviations
686
+
687
+ For any query involving US state data, always use `UPPER()` on state abbreviation columns in both SELECT and GROUP BY:
688
+ ```sql
689
+ SELECT UPPER(shipping.state_region) as state_abbr, ...
690
+ FROM ...
691
+ GROUP BY UPPER(shipping.state_region)
692
+ ```
693
+
694
+ ### Geo Maps
695
+
696
+ USA geo maps are the ONLY type of geo maps you can create. Never attempt world maps or other regional maps.
697
+
698
+ ### Content Assignments (uc_items)
699
+
700
+ `content.assignments` is a REPEATED RECORD. Always use UNNEST:
701
+ ```sql
702
+ SELECT
703
+ i.merchant_item_id,
704
+ assignment_record.host as storefront_host,
705
+ assignment_record.group_path as page_path
706
+ FROM `project.dataset.uc_items` i,
707
+ UNNEST(content.assignments) as assignment_record
708
+ WHERE content.assignments IS NOT NULL
709
+ ```
710
+
711
+ **URL construction pattern:**
712
+ ```sql
713
+ CONCAT(
714
+ 'https://',
715
+ assignment_record.host,
716
+ assignment_record.group_path,
717
+ COALESCE(NULLIF(assignment_record.url_part, ''), i.merchant_item_id),
718
+ '.html'
719
+ ) as full_item_url
720
+ ```
721
+
722
+ ### Division
723
+
724
+ Always use `SAFE_DIVIDE` for division operations to avoid division-by-zero errors.
725
+
726
+ ### SQL Comment Standards
727
+
728
+ Every SQL query must include comprehensive business-analyst-level comments:
729
+
730
+ 1. **Query header block** with descriptive report name and purpose
731
+ 2. **Section-based field comments** grouped by business topic, with examples in parentheses
732
+ 3. **Complex logic explanations** in business terms, not technical jargon
733
+ 4. **Data source descriptions** explaining what each table contains
734
+ 5. **Technical concept translations** (UNNEST = "flatten the array", etc.)
735
+ 6. **End-of-query business summary** with purpose and common use cases
736
+
737
+ Use accessible language. Explain the "why" not just the "what." Write as if explaining to a business colleague.
738
+
739
+ ---
740
+
741
+ ## ECharts Function Contract
742
+
743
+ Every ECharts visualization implements this exact function signature:
744
+
745
+ ```javascript
746
+ function formatChartData(data, isDashboard) {
747
+ // data: Array of objects from BigQuery query results
748
+ // isDashboard: boolean
749
+ //
750
+ // isDashboard === true:
751
+ // - 200x200px viewport
752
+ // - No axis labels
753
+ // - No legend (legend: { show: false })
754
+ // - One KPI metric displayed
755
+ // - Minimal padding
756
+ // - Tooltips confined (tooltip.confine: true)
757
+ // - Small title at top, one KPI at bottom
758
+ //
759
+ // isDashboard === false:
760
+ // - Full visualization with axis labels, tooltips, legend
761
+ // - Professional appearance suitable for business presentations
762
+ //
763
+ // Returns: Apache ECharts 5.5 options object
764
+ //
765
+ // Must handle:
766
+ // - Empty, null, or undefined data array
767
+ // - Missing or invalid fields in data objects
768
+ // - Malformed dates
769
+ // - Multi-year data ranges
770
+ // - Data type coercion (strings to numbers)
771
+ //
772
+ // On invalid data: return ECharts-compatible config with user-friendly message
773
+ // Log errors to console for debugging, never affect rendering
774
+ }
775
+ ```
776
+
777
+ ---
778
+
779
+ ## ECharts Battle-Hardening Rules
780
+
781
+ Apply ALL of these rules to every chart function. These come from hard-won production experience.
782
+
783
+ ### Data Safety
784
+ - Validate data exists before rendering. If data is null, undefined, or empty array, return an ECharts config with a styled "No data available" message.
785
+ - Handle `null`, `undefined`, `NaN`, and empty strings in data arrays.
786
+ - Coerce numeric strings to numbers explicitly (e.g., `parseFloat(value) || 0`).
787
+ - Sort data before rendering when order matters. Do not rely on query order surviving JSON serialization.
788
+
789
+ ### Rendering Safety
790
+ - Wrap all ECharts option construction in try/catch. The catch block must return a valid ECharts config with a styled error message.
791
+ - Set explicit `grid` margins to prevent label clipping (e.g., `grid: { left: '15%', right: '10%', bottom: '15%', top: '15%' }`).
792
+ - Use `axisLabel.rotate` for long category labels (typically 30-45 degrees).
793
+ - Set `tooltip.confine: true` to prevent tooltips from overflowing the container.
794
+ - Always set explicit `width` and `height` on the chart container. Do not rely on auto-sizing, especially for headless rendering.
795
+
796
+ ### Chart Type Specifics
797
+ - **Bar/Line**: Handle empty series gracefully. Set reasonable `max` on value axes to prevent outlier distortion.
798
+ - **Pie**: Filter out zero and negative values before rendering. Limit to top N categories + "Other" bucket.
799
+ - **Time Series**: Use `xAxis.type: 'time'` with proper date parsing. Handle timezone offsets.
800
+ - **Stacked**: Ensure all series have the same categories in the same order.
801
+
802
+ ### Error Fallback
803
+ If chart generation fails, render a styled error message in the chart container, not an empty div or JavaScript error. Include the error text and a suggestion to retry.
804
+
805
+ ### USA Geo Maps
806
+
807
+ When creating geo map visualizations:
808
+
809
+ 1. **Always include the complete state abbreviation to full name mapping:**
810
+ ```javascript
811
+ const stateNameMap = {
812
+ 'AL': 'Alabama', 'AK': 'Alaska', 'AZ': 'Arizona', 'AR': 'Arkansas',
813
+ 'CA': 'California', 'CO': 'Colorado', 'CT': 'Connecticut', 'DE': 'Delaware',
814
+ 'FL': 'Florida', 'GA': 'Georgia', 'HI': 'Hawaii', 'ID': 'Idaho',
815
+ 'IL': 'Illinois', 'IN': 'Indiana', 'IA': 'Iowa', 'KS': 'Kansas',
816
+ 'KY': 'Kentucky', 'LA': 'Louisiana', 'ME': 'Maine', 'MD': 'Maryland',
817
+ 'MA': 'Massachusetts', 'MI': 'Michigan', 'MN': 'Minnesota', 'MS': 'Mississippi',
818
+ 'MO': 'Missouri', 'MT': 'Montana', 'NE': 'Nebraska', 'NV': 'Nevada',
819
+ 'NH': 'New Hampshire', 'NJ': 'New Jersey', 'NM': 'New Mexico', 'NY': 'New York',
820
+ 'NC': 'North Carolina', 'ND': 'North Dakota', 'OH': 'Ohio', 'OK': 'Oklahoma',
821
+ 'OR': 'Oregon', 'PA': 'Pennsylvania', 'RI': 'Rhode Island', 'SC': 'South Carolina',
822
+ 'SD': 'South Dakota', 'TN': 'Tennessee', 'TX': 'Texas', 'UT': 'Utah',
823
+ 'VT': 'Vermont', 'VA': 'Virginia', 'WA': 'Washington', 'WV': 'West Virginia',
824
+ 'WI': 'Wisconsin', 'WY': 'Wyoming', 'DC': 'District of Columbia'
825
+ };
826
+ ```
827
+
828
+ 2. **Use `geo` + `series` combination, not just series with map property:**
829
+ ```javascript
830
+ geo: {
831
+ map: 'USA',
832
+ roam: true,
833
+ scaleLimit: { min: 0.5, max: 3 },
834
+ zoom: 1.1,
835
+ itemStyle: { areaColor: '#f0f0f0', borderColor: '#999', borderWidth: 1 },
836
+ emphasis: {
837
+ itemStyle: { areaColor: '#ffd54f' },
838
+ label: { show: true, fontSize: 12, fontWeight: 'bold' }
839
+ }
840
+ },
841
+ series: [{
842
+ name: 'Data by State',
843
+ type: 'map',
844
+ geoIndex: 0, // CRITICAL: links to geo configuration
845
+ data: mapData
846
+ }]
847
+ ```
848
+
849
+ 3. **Data mapping pattern:**
850
+ ```javascript
851
+ const mapData = data.map(item => ({
852
+ name: stateNameMap[item.state_abbr] || item.state_abbr,
853
+ value: parseFloat(item.metric_value) || 0,
854
+ state_abbr: item.state_abbr
855
+ }));
856
+ ```
857
+
858
+ 4. Include color-coded `visualMap`, interactive tooltips with formatted values, and a top-10 states summary as graphic elements.
859
+
860
+ ---
861
+
862
+ ## Business Analysis Prompt Template
863
+
864
+ After successful SQL testing and chart rendering, generate a system prompt for the analysis agent. This prompt is NOT the analysis itself -- it is a template that teaches a separate LLM how to analyze this specific report's data at execution time. Save it to `analysis_prompt.md`.
865
+
866
+ The analysis agent will receive: the final SQL query, the query results as JSON, and the chart PNG (if generated).
867
+
868
+ Follow this structure, adapted to the actual query topic:
869
+
870
+ ```markdown
871
+ # UltraCart [Specific Topic] Analysis - System Prompt
872
+
873
+ ## Overview
874
+ [Brief description: "You are an expert analyst specializing in UltraCart e-commerce [topic]. You will receive JSON data and a corresponding PNG visualization showing [what the chart shows]."]
875
+
876
+ ## Data Structure Understanding
877
+
878
+ ### Source Query Context
879
+ - **Table(s)**: [List relevant tables]
880
+ - **Key Technique**: [Important query elements, e.g., "Aggregates by date with timezone conversion"]
881
+ - **Partition Strategy**: [Summarize optimization used]
882
+ - **Timezone Handling**: [Note conversions applied]
883
+
884
+ ### JSON Data Fields
885
+ [List each field with type and description:]
886
+ - **field_name** (TYPE): Business description of the field
887
+ [Repeat for all fields]
888
+
889
+ ### Business Context
890
+ [Explain e-commerce relevance of this data]
891
+
892
+ ## Visualization Analysis Framework
893
+
894
+ ### Chart Type and Structure
895
+ - [Describe the chart: type, axes, series]
896
+
897
+ ### Visual Elements to Interpret
898
+ 1. [Trends, comparisons, anomalies to look for]
899
+ [If no chart: "No visualization provided; focus on data-driven insights."]
900
+
901
+ ## Analysis Methodology
902
+
903
+ ### 1. Overall Performance Assessment
904
+ [Subsections for totals, growth rates, volatility]
905
+
906
+ ### 2. Dimensional Breakdown
907
+ [e.g., by category, storefront, time period]
908
+
909
+ ### 3. Trend Pattern Recognition
910
+ [Seasonal patterns, growth trajectories, anomalies]
911
+
912
+ ### 4. Business Impact Insights
913
+ [Revenue implications, customer behavior, operational efficiency]
914
+
915
+ ## Key Metrics to Calculate and Report
916
+
917
+ ### Primary Metrics
918
+ [List with formulas, e.g., "Total Revenue: Sum of total_revenue"]
919
+
920
+ ### Comparative Metrics
921
+ [e.g., "Growth Rate: (Current - Previous) / Previous * 100"]
922
+
923
+ ## Alert Conditions and Red Flags
924
+
925
+ ### Performance Issues
926
+ [e.g., "Revenue drops >30% period-over-period"]
927
+
928
+ ### Data Quality Issues
929
+ [e.g., "Negative values in revenue metrics", "Missing date ranges"]
930
+
931
+ ## Actionable Recommendations Framework
932
+
933
+ ### High-Level Strategy
934
+ [General strategic advice based on data patterns]
935
+
936
+ ### Tactical Actions
937
+ [Specific actions tied to observed patterns]
938
+
939
+ ## Expected Output Structure
940
+ 1. Executive Summary (2-3 paragraphs)
941
+ 2. Key Findings (bulleted list with supporting data)
942
+ 3. Trend Analysis (with specific numbers)
943
+ 4. Anomalies and Alerts (if any)
944
+ 5. Recommendations (prioritized by impact)
945
+ 6. Data Quality Notes (if applicable)
946
+
947
+ Remember: Focus on actionable insights that drive e-commerce growth. Reference specific numbers from the data. Avoid vague generalizations.
948
+ ```
949
+
950
+ ---
951
+
952
+ ## Report Manifest and Replay
953
+
954
+ After creating a report, save a `report.yaml` manifest that captures the full definition. This manifest enables replay without LLM involvement (except for the executive analysis step).
955
+
956
+ ### Report Output Structure
957
+
958
+ ```
959
+ ./reports/<merchant_id>/
960
+ <report-name>/
961
+ report.yaml # Report manifest (replayable definition)
962
+ query.sql # Parameterized SQL with @parameter placeholders
963
+ chart.js # Battle-hardened formatChartData function
964
+ chart.png # Full ECharts visualization (PNG)
965
+ chart-dashboard.png # 200x200 dashboard thumbnail (PNG)
966
+ report.pdf # Combined PDF with chart + executive analysis (shareable)
967
+ analysis_prompt.md # System prompt template for analysis agent
968
+ report.md # Executive analysis (regenerated on each run)
969
+ data.json # Raw query results (optional)
970
+ decks/
971
+ <deck-name>.yaml # Deck definition (which reports to combine)
972
+ <deck-name>.pdf # Generated deck PDF (cover + TOC + all reports)
973
+ ```
974
+
975
+ ### Manifest Structure
976
+
977
+ ```yaml
978
+ name: "Revenue by Product Category"
979
+ description: "Daily revenue trends broken down by product category"
980
+ created: 2026-03-28
981
+ last_run: 2026-03-28
982
+ merchant_id: "DEMO"
983
+
984
+ prompt: "Show me revenue trends by product category for the last 90 days"
985
+ refinements:
986
+ - "Exclude gift cards from the category breakdown"
987
+ - "Use a stacked area chart instead of bars"
988
+
989
+ parameters:
990
+ - name: start_date
991
+ type: date
992
+ label: "Start Date"
993
+ description: "Beginning of the reporting period"
994
+ required: true
995
+ default: "-90d" # Relative: 90 days ago from today
996
+
997
+ - name: end_date
998
+ type: date
999
+ label: "End Date"
1000
+ description: "End of the reporting period"
1001
+ required: true
1002
+ default: "today"
1003
+
1004
+ - name: category_filter
1005
+ type: enum
1006
+ label: "Category Filter"
1007
+ description: "Which product categories to include"
1008
+ required: false
1009
+ options: ["All", "Electronics", "Apparel"]
1010
+ default: "All"
1011
+
1012
+ run_history:
1013
+ - run_date: 2026-03-28
1014
+ parameters:
1015
+ start_date: "2025-12-28"
1016
+ end_date: "2026-03-28"
1017
+ category_filter: "All"
1018
+
1019
+ delivery: # Optional: auto-deliver on --deliver
1020
+ slack:
1021
+ channels: ["C0123456789"] # Slack channel ID(s)
1022
+ email:
1023
+ to: ["ceo@example.com"] # Recipient list
1024
+ subject: "Weekly: Revenue Report" # Optional (defaults to report name)
1025
+ provider: "sendgrid" # sendgrid | postmark | mailgun | resend | ses
1026
+
1027
+ config:
1028
+ merchant_id: "DEMO"
1029
+ project_id: "ultracart-dw-demo" # Derived from merchant ID
1030
+ taxonomy_level: "medium"
1031
+ dataset: "ultracart_dw"
1032
+ tables_used:
1033
+ - "uc_orders"
1034
+ - "uc_items"
1035
+ external_tables_used: [] # e.g., ["my-marketing-warehouse.google_ads_data.funnel_data"]
1036
+
1037
+ sql_file: "query.sql"
1038
+
1039
+ chart:
1040
+ type: "stacked-area"
1041
+ echarts_file: "chart.js"
1042
+ output_format: "png"
1043
+ width: 1200
1044
+ height: 600
1045
+
1046
+ analysis:
1047
+ include: true
1048
+ prompt_file: "analysis_prompt.md"
1049
+ output_file: "report.md"
1050
+ landscape: false # Set true for landscape PDF (wide charts, geo maps)
1051
+ ```
1052
+
1053
+ The `analysis.landscape` field persists the orientation preference per-report. Priority at runtime: CLI `--landscape` flag > manifest `analysis.landscape` > portrait default.
1054
+
1055
+ ### Parameter Types
1056
+
1057
+ - `date` -- supports relative expressions (all resolve at runtime relative to today):
1058
+ - **Anchors**: `today`, `yesterday`
1059
+ - **Offsets**: `-Nd` (N days ago), `-Nw` (N weeks ago), `-Nm` (N months ago), `-Ny` (N years ago)
1060
+ - **Start-of-period**: `start_of_week`, `start_of_month`, `start_of_quarter`, `start_of_year`
1061
+ - **Start-of-last-period**: `start_of_last_month`, `start_of_last_quarter`, `start_of_last_year`
1062
+ - **End-of-last-period**: `end_of_last_month`, `end_of_last_quarter`, `end_of_last_year`
1063
+ - `string` -- free text
1064
+ - `number` -- numeric value (supports min/max validation)
1065
+ - `boolean` -- true/false
1066
+ - `enum` -- one of a fixed set of `options`
1067
+
1068
+ ### Parameter Resolution Order (at replay time)
1069
+
1070
+ 1. CLI flags (highest priority): `--start_date=2026-01-01`
1071
+ 2. Deck parameters (for deck runs): values from the deck YAML `parameters` section
1072
+ 3. Defaults from report manifest: `"-90d"` resolves relative to today
1073
+ 4. Prompt user for any required params still missing
1074
+
1075
+ ### Replay Modes
1076
+
1077
+ | Mode | Schema | SQL Gen | ECharts Design | Battle-Harden | Render | Analysis |
1078
+ |------|:------:|:-------:|:--------------:|:-------------:|:------:|:--------:|
1079
+ | **New** (full pipeline) | Yes | Yes | Yes | Yes | Yes | Yes |
1080
+ | **Run** (replay) | No | No | No | No | Yes | Yes |
1081
+ | **Edit** (refine) | Maybe | Maybe | Maybe | Maybe | Yes | Yes |
1082
+
1083
+ On replay (`uc-bq run`), the only LLM cost is the executive analysis. Everything else is deterministic.
1084
+
1085
+ On edit (`uc-bq edit`), load the manifest, show the user the current state, and only re-run the pipeline steps affected by their change.
1086
+
1087
+ ---
1088
+
1089
+ ## Available Datasets
1090
+
1091
+ | Dataset | Description | PII | Access |
1092
+ |---------|-------------|-----|--------|
1093
+ | `ultracart_dw` | Standard data warehouse tables | No | All merchants |
1094
+ | `ultracart_dw_medium` | Medium-sensitivity tables (includes PII) | Yes | Requires medium+ taxonomy permission |
1095
+ | `ultracart_dw_streaming` | Analytics sessions + screen recordings | No | All merchants (large tables, separated for performance) |
1096
+ | `ultracart_dw_linked` | Parent/child aggregated data | No | Parent accounts only |
1097
+ | `ultracart_dw_linked_medium` | Linked data with PII | Yes | Parent accounts with medium+ permission |
1098
+
1099
+ ---
1100
+
1101
+ ## Taxonomy Levels
1102
+
1103
+ The taxonomy level determines which columns are visible in BigQuery views. It is configured in `.ultracart-bq.json`.
1104
+
1105
+ | Level | Description |
1106
+ |-------|-------------|
1107
+ | `standard` | Baseline fields, no PII. Available to all users. |
1108
+ | `low` | Standard + minimal sensitive data. |
1109
+ | `medium` | Low + moderate PII (email, addresses, phone, etc.). |
1110
+ | `high` | All fields, full access. |
1111
+
1112
+ Never allow queries against views above the configured taxonomy level. The merchant's UltraCart administrator assigns taxonomy levels.
1113
+
1114
+ ---
1115
+
1116
+ ## Available Tables
1117
+
1118
+ ### Standard Dataset (`ultracart_dw`)
1119
+
1120
+ | Table | Description |
1121
+ |-------|-------------|
1122
+ | `uc_orders` | Orders |
1123
+ | `uc_customers` | Customers |
1124
+ | `uc_items` | Items / product catalog |
1125
+ | `uc_auto_orders` | Subscriptions / auto-orders |
1126
+ | `uc_cart_abandons` | Abandoned cart records |
1127
+ | `uc_coupons` | Coupon configurations (codes, discount types, expiration, usage restrictions) |
1128
+ | `uc_gift_certificates` | Gift certificate tracking (codes, balances, issuance, redemption status) |
1129
+ | `uc_affiliates` | Affiliate accounts (IDs, contact details, account settings) |
1130
+ | `uc_affiliate_clicks` | Affiliate click tracking (timestamps, affiliate IDs, referral details) |
1131
+ | `uc_affiliate_ledgers` | Affiliate financial transactions (commissions, adjustments, payout statuses) |
1132
+ | `uc_affiliate_payments` | Affiliate payout records (amounts, dates, methods) |
1133
+ | `uc_affiliate_commission_groups` | Commission structures (rates, rules for affiliate payouts) |
1134
+ | `uc_affiliate_postback_logs` | Postback event logs (conversions, campaign performance tracking) |
1135
+ | `uc_affiliate_network_pixels` | Affiliate network pixel configurations |
1136
+ | `uc_affiliate_network_pixel_postback_logs` | Pixel server-to-server postback logs |
1137
+ | `uc_conversations` | Webchat and SMS conversations |
1138
+ | `uc_conversation_pbx_calls` | Phone call records from PBX module |
1139
+ | `uc_storefronts` | Storefront configurations (domain, theme, operational settings) |
1140
+ | `uc_storefront_customers` | StoreFront Communications customer records (email/SMS campaign history) |
1141
+ | `uc_storefront_pages` | Storefront page data (URLs, assigned items, permissions) |
1142
+ | `uc_storefront_experiments` | A/B testing data (configurations, variants, performance metrics) |
1143
+ | `uc_storefront_upsell_offers` | Upsell offer details (descriptions, conditions, products/discounts) |
1144
+ | `uc_storefront_upsell_offer_events` | Upsell interaction events (views, clicks, conversions) |
1145
+ | `uc_storefront_upsell_paths` | Upsell path sequences (eligibility rules, display order) |
1146
+ | `uc_item_inventory_history` | Inventory level history (restocks, sales, adjustments over time) |
1147
+ | `uc_shipping_methods` | Shipping method configs (carrier names, costs, delivery times, rules) |
1148
+ | `uc_fraud_rules` | Fraud detection rules (thresholds, patterns for flagging suspicious activity) |
1149
+ | `uc_rotating_transaction_gateways` | Payment gateway configs (priorities, routing rules) |
1150
+ | `uc_rotating_transaction_gateway_history` | Gateway transaction logs (assignments, outcomes) |
1151
+ | `uc_surveys` | Customer survey data (questions, responses, feedback metadata) |
1152
+ | `uc_workflow_tasks` | Workflow task tracking (assignments, status, comments) |
1153
+ | `uc_zoho_desk_tickets` | Customer service ticket archive from Zoho Desk |
1154
+
1155
+ ### Streaming Dataset (`ultracart_dw_streaming`)
1156
+
1157
+ | Table | Description |
1158
+ |-------|-------------|
1159
+ | `uc_analytics_session_streaming` | Detailed analytics sessions. Large table. Each session contains a collection of events (hits). |
1160
+ | `uc_screen_recording_streaming` | Screen recording session metadata from StoreFront screen recording system. |
1161
+
1162
+ ---
1163
+
1164
+ ## Handling Existing SQL
1165
+
1166
+ If the user provides an existing SQL query (recognizable by SELECT, FROM, WHERE syntax):
1167
+
1168
+ 1. Acknowledge the existing query and review it
1169
+ 2. Ask clarifying questions: What improvements are needed? Performance? New functionality? Bug fix?
1170
+ 3. Wait for the user's response before making changes
1171
+ 4. For minor tweaks (syntax, formatting, small optimizations): make direct improvements and test
1172
+ 5. For major changes (new tables, date logic changes, rewrites): follow the full mandatory analysis sections
1173
+
1174
+ **Skip mandatory analysis sections only if** the user wants minor fixes and no new date logic or tables are being added.
1175
+
1176
+ ---
1177
+
1178
+ ## External Table Schema Discovery
1179
+
1180
+ When you encounter a fully qualified BigQuery table name that is NOT in the merchant's standard project (e.g., `external-project.dataset.table_name`):
1181
+
1182
+ 1. Run a `SELECT * FROM \`full.qualified.table_name\` LIMIT 1` query via `uc-bq query` to discover the schema
1183
+ 2. Analyze the returned columns, types, and sample values
1184
+ 3. Skip standard schema discovery steps (those are for UltraCart tables only)
1185
+ 4. Proceed directly to the mandatory analysis sections
1186
+ 5. Apply standard date/datetime rules based on discovered column types
1187
+ 6. External tables may not have `partition_date` columns -- adjust accordingly
1188
+
1189
+ ---
1190
+
1191
+ ## Cross-Project Query Patterns
1192
+
1193
+ When a report requires joining UltraCart data with external project data:
1194
+
1195
+ ### Discovery
1196
+
1197
+ 1. Check the merchant's `external_projects` config for available tables and their aliases
1198
+ 2. Use `uc-bq schema --tables=alias.dataset.table` to get external table schemas (e.g., `uc-bq schema --tables=marketing.google_ads_data.funnel_data`)
1199
+ 3. Use `uc-bq schema --project=some-gcp-project` to browse tables in an unregistered project
1200
+
1201
+ ### SQL Patterns
1202
+
1203
+ Always use fully qualified table names with backtick quoting for cross-project joins:
1204
+
1205
+ ```sql
1206
+ -- Join UltraCart orders with external marketing data
1207
+ SELECT
1208
+ o.order_id,
1209
+ o.total,
1210
+ f.campaign_name,
1211
+ f.ad_spend
1212
+ FROM `ultracart-dw-demo.ultracart_dw.uc_orders` o
1213
+ JOIN `my-marketing-warehouse.google_ads_data.funnel_data` f
1214
+ ON DATE(DATETIME(TIMESTAMP(o.creation_dts), 'America/New_York')) = f.date
1215
+ AND o.utm_campaign = f.campaign_id
1216
+ WHERE o.creation_dts BETWEEN
1217
+ DATETIME(TIMESTAMP(CAST(@start_date AS DATETIME), 'America/New_York'))
1218
+ AND DATETIME(TIMESTAMP(CAST(@end_date AS DATETIME), 'America/New_York'))
1219
+ AND o.partition_date >= DATE_TRUNC(DATE_SUB(@start_date, INTERVAL 1 MONTH), WEEK(SUNDAY))
1220
+ AND o.partition_date <= DATE_TRUNC(DATE_ADD(@end_date, INTERVAL 1 MONTH), WEEK(SUNDAY))
1221
+ ```
1222
+
1223
+ ### Key Rules for Cross-Project Queries
1224
+
1225
+ - UltraCart table side: apply all standard partition optimization and datetime conversion rules
1226
+ - External table side: no partition_date assumptions; adapt to the external table's schema
1227
+ - Always include both project IDs in the manifest's `config.tables_used` and `config.external_tables_used`
1228
+ - Date join conditions may need type casting between UltraCart DATETIME columns and external DATE/TIMESTAMP columns