lkr-dev-cli 0.0.58__tar.gz → 0.0.59__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 (176) hide show
  1. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/SKILL.md +461 -0
  2. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/01-simple-single-table.md +76 -0
  3. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/02-conditional-measures-and-bucketing.md +159 -0
  4. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/03-pk-fk-joins.md +136 -0
  5. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/04-triangle-inequality-joins.md +97 -0
  6. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/05-dimensional-non-key-joins.md +126 -0
  7. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/06-nested-ctes-and-subqueries.md +138 -0
  8. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/07-measure-of-measures-reaggregation.md +116 -0
  9. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/08-window-functions-and-qualify.md +143 -0
  10. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/09-unions.md +124 -0
  11. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/10-unnest-arrays.md +163 -0
  12. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/11-json-columns-and-extract.md +166 -0
  13. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/12-table-wildcards.md +146 -0
  14. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/13-large-tables-and-partitioning.md +165 -0
  15. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml/references/14-templated-filters-and-parameters.md +123 -0
  16. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml-workflow/SKILL.md +168 -0
  17. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml-workflow/scripts/create_github_pr.sh +56 -0
  18. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml-workflow/scripts/init_branch.py +32 -0
  19. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml-workflow/scripts/init_sql_queries.py +49 -0
  20. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml-workflow/scripts/preflight.sh +19 -0
  21. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml-workflow/scripts/record_query_parity.py +107 -0
  22. lkr_dev_cli-0.0.59/.agents/skills/agentic-lookml-workflow/scripts/validate_and_commit_branch.py +63 -0
  23. lkr_dev_cli-0.0.59/.agents/skills/generate-view-from-table/SKILL.md +98 -0
  24. lkr_dev_cli-0.0.59/.agents/skills/generate-view-from-table/scripts/generate_bronze_view.py +29 -0
  25. lkr_dev_cli-0.0.59/.agents/skills/lookml/SKILL.md +24 -0
  26. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-access-grants/SKILL.md +48 -0
  27. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-explore/SKILL.md +161 -0
  28. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-explore/references/advanced.md +55 -0
  29. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-explore/references/joins.md +65 -0
  30. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-fields/SKILL.md +117 -0
  31. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-fields/references/dimension.md +88 -0
  32. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-fields/references/dimension_group.md +111 -0
  33. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-fields/references/filter_parameter.md +73 -0
  34. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-fields/references/measure.md +75 -0
  35. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-fields/references/value_format.md +201 -0
  36. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-liquid/SKILL.md +182 -0
  37. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-model/SKILL.md +117 -0
  38. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-refinements/SKILL.md +117 -0
  39. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-sets/SKILL.md +115 -0
  40. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-tests/SKILL.md +105 -0
  41. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-view/SKILL.md +97 -0
  42. lkr_dev_cli-0.0.59/.agents/skills/lookml/sub-skills/lookml-view/references/derived_table.md +179 -0
  43. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.github/workflows/ci.yml +3 -3
  44. lkr_dev_cli-0.0.59/.github/workflows/lookml-pr-checks.yml +61 -0
  45. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.github/workflows/release.yml +5 -5
  46. lkr_dev_cli-0.0.59/.github/workflows/update-looker-skills.yml +87 -0
  47. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.pre-commit-config.yaml +2 -2
  48. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/Makefile +2 -2
  49. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/PKG-INFO +16 -6
  50. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/README.md +15 -5
  51. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/auth/main.py +52 -17
  52. lkr_dev_cli-0.0.59/lkr/auth/oauth.py +377 -0
  53. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/auth_service.py +127 -43
  54. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/classes.py +2 -0
  55. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/codemode/main.py +1 -0
  56. lkr_dev_cli-0.0.59/lkr/constants.py +7 -0
  57. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/extended_sdk_methods/__init__.py +5 -1
  58. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/extended_sdk_methods/main.py +93 -10
  59. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/main.py +5 -4
  60. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/parse/__init__.py +102 -0
  61. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/parse/constants.py +5 -0
  62. lkr_dev_cli-0.0.59/lkr/parse/dag.py +508 -0
  63. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/parse/lookml.py +36 -5
  64. lkr_dev_cli-0.0.59/lkr/parse/lookml_database_compare.py +988 -0
  65. lkr_dev_cli-0.0.59/lkr/parse/lookml_snapshot.py +668 -0
  66. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/parse/lookml_to_api.py +1 -5
  67. lkr_dev_cli-0.0.59/lkr/parse/main.py +636 -0
  68. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/parse/sql.py +186 -0
  69. lkr_dev_cli-0.0.59/lkr/parse/sql_to_lookml.py +2874 -0
  70. lkr_dev_cli-0.0.59/lkr/parse/translations.py +1049 -0
  71. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/parse/types.py +211 -1
  72. lkr_dev_cli-0.0.59/lkr/parse/user_attributes.py +1035 -0
  73. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/tools/lookml.py +585 -1
  74. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr.md +167 -32
  75. lkr_dev_cli-0.0.59/parse.md +65 -0
  76. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/pyproject.toml +1 -1
  77. lkr_dev_cli-0.0.59/skills-lock.json +59 -0
  78. lkr_dev_cli-0.0.59/tests/test_bq_change_listener.py +281 -0
  79. lkr_dev_cli-0.0.59/tests/test_lookml_database_compare.py +350 -0
  80. lkr_dev_cli-0.0.59/tests/test_lookml_snapshot.py +313 -0
  81. lkr_dev_cli-0.0.59/tests/test_oauth_account.py +445 -0
  82. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_parse_lookml.py +347 -0
  83. lkr_dev_cli-0.0.59/tests/test_sql_join_analysis.py +452 -0
  84. lkr_dev_cli-0.0.59/tests/test_sql_queries_workflow.py +76 -0
  85. lkr_dev_cli-0.0.59/tests/test_translations.py +415 -0
  86. lkr_dev_cli-0.0.59/tests/test_user_attributes.py +471 -0
  87. lkr_dev_cli-0.0.58/lkr/auth/oauth.py +0 -254
  88. lkr_dev_cli-0.0.58/lkr/constants.py +0 -2
  89. lkr_dev_cli-0.0.58/lkr/parse/dag.py +0 -271
  90. lkr_dev_cli-0.0.58/lkr/parse/main.py +0 -270
  91. lkr_dev_cli-0.0.58/lkr/parse/sql_to_lookml.py +0 -1352
  92. lkr_dev_cli-0.0.58/tests/test_oauth_account.py +0 -124
  93. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-code-mode/SKILL.md +0 -0
  94. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-code-mode/scripts/examples.py +0 -0
  95. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-code-mode/scripts/order_items_query.py +0 -0
  96. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-code-mode/scripts/personal_folder_dashboards.py +0 -0
  97. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-code-mode/scripts/readme.py +0 -0
  98. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-code-mode/scripts/traverse_folders.py +0 -0
  99. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/SKILL.md +0 -0
  100. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/bq_view_lookml_generator.py +0 -0
  101. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/configure_model.py +0 -0
  102. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/create_custom_view.py +0 -0
  103. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/deploy_and_reset_workspace.py +0 -0
  104. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/ensure_project_and_model.py +0 -0
  105. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/generate_lookml.py +0 -0
  106. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/init_project.py +0 -0
  107. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/inspect_connections.py +0 -0
  108. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/migrate_dashboard_to_udd.py +0 -0
  109. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/upload_assets.py +0 -0
  110. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.agents/skills/lkr-project-helper/scripts/validate_and_commit.py +0 -0
  111. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.github/workflows/website.yml +0 -0
  112. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.gitignore +0 -0
  113. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.python-version +0 -0
  114. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.vscode/launch.json +0 -0
  115. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/.vscode/settings.json +0 -0
  116. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/AGENTS.md +0 -0
  117. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/Dockerfile +0 -0
  118. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/LICENSE +0 -0
  119. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/TODO.md +0 -0
  120. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/cloudbuild.yaml +0 -0
  121. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/codemode.md +0 -0
  122. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/__init__.py +0 -0
  123. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/auth/__init__.py +0 -0
  124. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/codemode/LOCAL.md +0 -0
  125. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/codemode/__init__.py +0 -0
  126. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/codemode/constant.py +0 -0
  127. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/codemode/download_swagger.py +0 -0
  128. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/codemode/examples.py +0 -0
  129. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/codemode/help.py +0 -0
  130. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/codemode/readme.py +0 -0
  131. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/codemode/swagger.json +0 -0
  132. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/codemode/type.py +0 -0
  133. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/custom_types.py +0 -0
  134. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/exceptions.py +0 -0
  135. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/extended_sdk_methods/classes.py +0 -0
  136. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/logger.py +0 -0
  137. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/mcp/classes.py +0 -0
  138. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/mcp/main.py +0 -0
  139. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/mcp/utils.py +0 -0
  140. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/observability/classes.py +0 -0
  141. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/observability/embed_container.html +0 -0
  142. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/observability/main.py +0 -0
  143. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/observability/utils.py +0 -0
  144. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/__init__.py +0 -0
  145. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/_schema_rs.so +0 -0
  146. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/builder.py +0 -0
  147. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/dynamic_fields.py +0 -0
  148. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/fields.py +0 -0
  149. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/filter_expressions.py +0 -0
  150. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/main.py +0 -0
  151. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/rust/Cargo.lock +0 -0
  152. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/rust/Cargo.toml +0 -0
  153. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/rust/src/lib.rs +0 -0
  154. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/schema/validator.py +0 -0
  155. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/tools/agents.py +0 -0
  156. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/tools/classes.py +0 -0
  157. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/tools/main.py +0 -0
  158. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr/tools/permission_deprecation.py +0 -0
  159. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/lkr_dev_cli/__init__.py +0 -0
  160. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/scripts/schema/README.md +0 -0
  161. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/scripts/schema/__init__.py +0 -0
  162. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/scripts/schema/history.py +0 -0
  163. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/TESTING.md +0 -0
  164. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_agents_tools.py +0 -0
  165. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_codemode.py +0 -0
  166. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_dependency_resolution.py +0 -0
  167. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_deps.sh +0 -0
  168. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_extended_sdk_methods.py +0 -0
  169. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_init_sdk.py +0 -0
  170. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_lookml_tools.py +0 -0
  171. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_parse_sql.py +0 -0
  172. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_permission_deprecation.py +0 -0
  173. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_schema.py +0 -0
  174. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/tests/test_version.py +0 -0
  175. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/ty.toml +0 -0
  176. {lkr_dev_cli-0.0.58 → lkr_dev_cli-0.0.59}/uv.lock +0 -0
@@ -0,0 +1,461 @@
1
+ ---
2
+ name: agentic-lookml
3
+ description: >
4
+ Unified LookML engineering, SQL-to-LookML refactoring, and Medallion semantic modeling skill (`agentic-lookml`).
5
+ Combines the 14 SQL-to-LookML pattern playbooks (`lkr parse sql-lookml-compare`, Canonical Looker Query gate,
6
+ Query-layer Table Calculations and Custom Fields with `matches_filter`, and Bronze/Silver/Gold Medallion architecture)
7
+ with the LookML writing reference skills from `looker-open-source/looker-skills` into a single reference set.
8
+ Use when authoring LookML models, views, explores, fields, refinements, Liquid, sets, access grants, or data tests,
9
+ or when refactoring SQL queries into LookML.
10
+ ---
11
+
12
+ # Agentic LookML (`agentic-lookml`)
13
+
14
+ ## Overview and goal
15
+
16
+ When authoring LookML from scratch, migrating analyst SQL queries, auditing existing LookML projects, or expanding semantic models, agents need both a deterministic architectural blueprint and a complete LookML syntax reference in one place.
17
+
18
+ The `agentic-lookml` skill combines two complementary skill sets into a single unified reference:
19
+
20
+ - The 14 SQL-to-LookML refactoring pattern playbooks in `references/`, which pair with `lkr parse sql-lookml-compare` to evaluate SQL queries against an existing LookML project graph, separate Query-layer calculations (Quick Start `query:` blocks, Custom Fields, and Table Calculations) from LookML-layer semantics, and route every LookML change into a scalable Bronze, Silver, and Gold Medallion hierarchy.
21
+ - The 9 LookML writing reference skills from [looker-open-source/looker-skills](https://github.com/looker-open-source/looker-skills) packaged under [lookml](../lookml/SKILL.md) (`.agents/skills/lookml/sub-skills/*/SKILL.md`), which provide syntax rules and examples for models, views, derived tables, explores, joins, dimensions, dimension groups, measures, filters, parameters, value formats, refinements, Liquid variables, sets, access grants, and LookML data tests.
22
+
23
+ ## Unified reference index
24
+
25
+ ### SQL-to-LookML pattern playbooks (`references/`)
26
+
27
+ Each SQL query analyzed by `lkr parse sql-lookml-compare` can match one or multiple pattern categories at the same time (such as `["pk_fk_joins", "json_columns_and_extract", "window_functions_and_qualify"]`). Consult the matching playbook in `references/` for the AST signals, Medallion layer routing, and complete LookML templates:
28
+
29
+ | # | Category ID | Pattern playbook | AST trigger summary | Target layer |
30
+ | :--- | :--- | :--- | :--- | :--- |
31
+ | 01 | `simple_single_table` | [01-simple-single-table.md](references/01-simple-single-table.md) | 1 table, no joins/CTEs/unions/windows, standard columns and single-pass aggregates | `bronze/:table.refinement.lkml` + `silver/explores/` (fact-anchored) |
32
+ | 02 | `conditional_measures_and_bucketing` | [02-conditional-measures-and-bucketing.md](references/02-conditional-measures-and-bucketing.md) | `SUM(CASE WHEN ...)`, `COUNTIF`, row-level `CASE WHEN` buckets, or measure-to-measure ratios | Query Layer (`Custom Field` with `matches_filter` / `Table Calc`), `bronze/:table.refinement.lkml` (single-table DAG), or `silver/:view_a_:view_b_fields.view.lkml` (multi-view DAG, bare-joined) |
33
+ | 03 | `pk_fk_joins` | [03-pk-fk-joins.md](references/03-pk-fk-joins.md) | Equality joins (`t1.fk = t2.pk`) between entity and fact tables | `silver/explores/:name.explore.lkml` (`type: left_outer`) + `bronze/:table.refinement.lkml` (PKs) + `silver/:view_a_:view_b_fields.view.lkml` (bare-joined cross-view fields) |
34
+ | 04 | `triangle_inequality_joins` | [04-triangle-inequality-joins.md](references/04-triangle-inequality-joins.md) | Join `ON` contains `<, <=, >, >=, BETWEEN` (SCD2, rolling windows, point-in-time `AS OF`) | Strictly `gold/explores/` (`extends: [silver]`, `tags: ["gold"]`) + Gold `AS OF` view or `bronze/calendar_dates.view.lkml` |
35
+ | 05 | `dimensional_non_key_joins` | [05-dimensional-non-key-joins.md](references/05-dimensional-non-key-joins.md) | Joins on non-unique attributes (`date = date`, `country = country`) or `FULL OUTER JOIN` across facts | Gold OJOF (`FULL OUTER JOIN ON FALSE` + `COALESCE` view), Silver `UNION ALL` SDT (overlapping schemas), Silver NDT (sessionization), or `bronze/calendar_dates.view.lkml` |
36
+ | 06 | `nested_ctes_and_subqueries` | [06-nested-ctes-and-subqueries.md](references/06-nested-ctes-and-subqueries.md) | Pass-through CTEs or subqueries doing renames, scalar math, row filters, or `PK = FK` joins without grain changes | Flatten into `bronze/:table.refinement.lkml` + `silver/explores/` (0 Derived Tables) |
37
+ | 07 | `measure_of_measures_reaggregation` | [07-measure-of-measures-reaggregation.md](references/07-measure-of-measures-reaggregation.md) | Outer query aggregates, tiers, or filters on an inner `GROUP BY` aggregate column | `silver/:entity_facts.view.lkml` (consolidated ephemeral NDT `explore_source:`) or `gold/` if `bind_all_filters: yes` |
38
+ | 08 | `window_functions_and_qualify` | [08-window-functions-and-qualify.md](references/08-window-functions-and-qualify.md) | `OVER (PARTITION BY ... ORDER BY ...)` (`ROW_NUMBER`, `RANK`, `LAG`, `LEAD`) or `QUALIFY` | Query Layer (`Table Calc` + Quick Start `query:`), `silver/` NDT (`derived_column:` post-`GROUP BY`), `silver/` SDT (`extends: [base_view]` with `${EXTENDED}` pre-`GROUP BY` or `QUALIFY`), or `gold/` (`bind_all_filters: yes`) |
39
+ | 09 | `unions` | [09-unions.md](references/09-unions.md) | `UNION ALL` or `UNION DISTINCT` across tables or CTEs | `silver/:name.view.lkml` (SDT with `${}` on everything, `extends: [base_view]`, `fields: [ALL_FIELDS*, -view.unneeded]`, compound PK) or `gold/` (Long Metrics unpivot) |
40
+ | 10 | `unnest_arrays` | [10-unnest-arrays.md](references/10-unnest-arrays.md) | `UNNEST(...)`, `CROSS/LEFT JOIN UNNEST`, or correlated `FROM UNNEST` scalar subqueries | `bronze/:table.refinement.lkml` (`1:1` inline using `${dimension}`) or `bronze/:table__:arr.view.lkml` (`WITH OFFSET` PK) + `bronze/explores/` and `silver/explores/` (`1:M`) |
41
+ | 11 | `json_columns_and_extract` | [11-json-columns-and-extract.md](references/11-json-columns-and-extract.md) | `JSON_VALUE`, `JSON_EXTRACT_SCALAR`, `JSON_QUERY_ARRAY`, `LAX_*`, or JSON path access | `bronze/:table.refinement.lkml` (`hidden: yes`, `suggestable: no`) + `bronze/:table__:col.view.lkml` (`SAFE_CAST`) + `bronze/explores/` (bare join `sql: ;;`) + `silver/explores/` |
42
+ | 12 | `table_wildcards` | [12-table-wildcards.md](references/12-table-wildcards.md) | `FROM table_*` or `_TABLE_SUFFIX` predicates | `bronze/` (`table_*` view, refinement, base explore with Liquid `_TABLE_SUFFIX` pruning + `always_filter:`) + `silver/explores/` (`extends: [base]`) |
43
+ | 13 | `large_tables_and_partitioning` | [13-large-tables-and-partitioning.md](references/13-large-tables-and-partitioning.md) | Partitioned/clustered tables, non-SARGable date wrappers (`DATE(ts)`), `APPROX_COUNT_DISTINCT` | `bronze/` (actual column `dimension_group`, partition min date/clustering descriptions in refinement, `always_filter:` in base explore) + `silver/explores/` (`extends: [base]`) + `gold/explores/` (`aggregate_table`) |
44
+ | 14 | `templated_filters_and_parameters` | [14-templated-filters-and-parameters.md](references/14-templated-filters-and-parameters.md) | Runtime filters required inside inner subqueries/cohorts (`{% condition %}`, `{% parameter %}`) | Strictly `gold/` and `gold/explores/` (`tags: ["gold"]`) paired with mandatory `always_filter:` |
45
+
46
+ ### LookML writing reference skills (`looker-open-source/looker-skills`)
47
+
48
+ When writing or editing specific LookML constructs, consult the installed `looker-open-source/looker-skills` reference files alongside the Medallion placement rules below. Where the upstream syntax guides use generic `/views/` or `/explores/` folder paths in their examples, always apply the Medallion directory conventions (`bronze/`, `silver/`, `gold/`, `.refinement.lkml`, and `silver/:view_a_:view_b_fields.view.lkml`) defined in this skill:
49
+
50
+ | LookML domain | Primary skill reference | Sub-references | Medallion architectural rule |
51
+ | :--- | :--- | :--- | :--- |
52
+ | LookML syntax reference hub | [lookml](../lookml/SKILL.md) | `sub-skills/*/SKILL.md` | Index of all `looker-open-source/looker-skills` sub-skills and their descriptions. |
53
+ | End-to-end SQL-to-LookML session & PR workflow | [agentic-lookml-workflow](../agentic-lookml-workflow/SKILL.md) | None | Orchestrates `--dev` authentication (`lkr auth add-access-token`), production reset and timestamped git branch creation (`YYYYMMDD_HHMMSS_mmm_<user_id>`), `./data/:id/` session tracking (`data.json`, `summary.md`, and `./data/:id/lookml`), `sql-lookml-compare`, `lookml push --no-reset`, `validate_project`, `run_lookml_test`, `commit`, and GitHub PR creation with `summary.md` attached. |
54
+ | Generating Bronze views from physical database tables | [generate-view-from-table](../generate-view-from-table/SKILL.md) | None | Whenever `lkr parse sql-lookml-compare` shows a physical table in `parsed_query.tables` with no matching entry in `views` (or with non-empty `views[].unmodeled_db_columns`), use `generate-view-from-table` (`lkr tools lookml create --folder bronze`, `generate_lookml_with_new_files`, or MCP `generate_view_from_table`) to autogenerate `bronze/:table.view.lkml` before writing any refinements or downstream LookML. |
55
+ | Model files (`.model.lkml`) and datagroups | [lookml-model](../lookml/sub-skills/lookml-model/SKILL.md) | None | Keep `project.model.lkml` clean: define `connection:`, `datagroup:` caching policies, and explicit `include:` statements for `bronze/explores/*`, `silver/explores/*`, and `gold/explores/*`. Define explores in dedicated `.explore.lkml` files rather than inline in the model file. |
56
+ | Views (`.view.lkml`) and derived tables | [lookml-view](../lookml/sub-skills/lookml-view/SKILL.md) | [derived_table.md](../lookml/sub-skills/lookml-view/references/derived_table.md) | Never hand-edit autogenerated `bronze/:table.view.lkml` files. Place consolidated entity-grain NDTs (`explore_source:`, ephemeral by default), `${EXTENDED}` window views, and `UNION ALL` SDTs in `silver/`. Place `bind_all_filters: yes` NDTs, Liquid SDTs, `AS OF` SDTs, and OJOF `COALESCE` views in `gold/`. Every SDT must use `${...}` substitution for all tables and fields. |
57
+ | Explores (`.explore.lkml`), joins, and aggregate tables | [lookml-explore](../lookml/sub-skills/lookml-explore/SKILL.md) | [joins.md](../lookml/sub-skills/lookml-explore/references/joins.md), [advanced.md](../lookml/sub-skills/lookml-explore/references/advanced.md) | Anchor `silver/explores/` on `FROM`-clause fact tables with strict `PK = FK` equality joins (`type: left_outer` default), explicit `view_name:`, and zero Liquid. Keep `UNNEST`, bare JSON joins, wildcard `_TABLE_SUFFIX` pruning, and partition `always_filter:` in `bronze/explores/` (`extension: required`). Isolate inequality joins, OJOF, Liquid, and `aggregate_table:` blocks in `gold/explores/` (`extends: [silver]`, `tags: ["gold"]`). |
58
+ | Fields (`dimension`, `dimension_group`, `measure`, `filter`, `parameter`) | [lookml-fields](../lookml/sub-skills/lookml-fields/SKILL.md) | [dimension.md](../lookml/sub-skills/lookml-fields/references/dimension.md), [dimension_group.md](../lookml/sub-skills/lookml-fields/references/dimension_group.md), [measure.md](../lookml/sub-skills/lookml-fields/references/measure.md), [filter_parameter.md](../lookml/sub-skills/lookml-fields/references/filter_parameter.md), [value_format.md](../lookml/sub-skills/lookml-fields/references/value_format.md) | Put single-table dimensions, `type: duration` dimension groups, and single-table measures in `bronze/:table.refinement.lkml`. Put any dimension or measure whose dependency DAG references multiple views into `silver/:view_a_:view_b_fields.view.lkml` and bare-join (`sql: ;;`) into the Explore. Restrict `filter:` and `parameter:` fields with Liquid SQL to `gold/`. |
59
+ | Refinements (`+view`, `+explore`) and `include:` layering | [lookml-refinements](../lookml/sub-skills/lookml-refinements/SKILL.md) | None | Create `bronze/:table.refinement.lkml` on-demand only (first line `include: ":table.view.lkml"`). Use `extends:` (not `+` refinement) when creating Gold specialty explores from Silver explores so the Silver explore remains unmodified. |
60
+ | Liquid variables (`{% condition %}`, `{% parameter %}`, `_in_query`) | [lookml-liquid](../lookml/sub-skills/lookml-liquid/SKILL.md) | None | Zero Liquid in Silver Explores. Restrict Liquid SQL to `gold/` views and explores (always paired with `always_filter:`), with the single exception of `_TABLE_SUFFIX` wildcard shard pruning in `bronze/explores/:prefix_base.explore.lkml`. Always use `_in_query` rather than `_is_selected` for table routing or conditional joins so totals do not break. |
61
+ | Sets (`set:`) and field visibility (`fields:`) | [lookml-sets](../lookml/sub-skills/lookml-sets/SKILL.md) | None | Use `set:` for reusable `drill_fields` and Explore-level field curation (`fields: [ALL_FIELDS*, -view.unmapped_col]`, especially on `UNION ALL` views that extend a base Bronze view). |
62
+ | Access grants (`access_grant:`, `required_access_grants:`) | [lookml-access-grants](../lookml/sub-skills/lookml-access-grants/SKILL.md) | None | Define `access_grant:` blocks in the model file and apply `required_access_grants:` on views, joins, explores, or sensitive dimensions/measures. |
63
+ | LookML data tests (`test:`) | [lookml-tests](../lookml/sub-skills/lookml-tests/SKILL.md) | None | Check `queries[].tests` from `lkr parse sql-lookml-compare` before modifying existing fields or joins, and add primary key uniqueness and business logic `test:` assertions when introducing complex joins or derived tables. |
64
+
65
+ ## How `lkr parse sql-lookml-compare` drives refactoring
66
+
67
+ The `lkr parse sql-lookml-compare` command parses both the SQL AST and the LookML project graph, outputting a `QueryLookmlMapping` object with four groups of signals:
68
+
69
+ - The `raw_sql` and `parsed_query` fields capture tables, CTEs, joins (`on_sql`, `kind`, `side`, normalized `join_type`, `equi_joins`, `non_equi_conditions`), projection columns (`is_agg`, `sql`, `table`, `column`, `except_`, `replace`, `rename`), `where`, `having`, `group_by`, `order_by`, and `limit`.
70
+ - The `views` list reports matched LookML views with file locations, matched `fields` (with transitive `${...}` and derived-table dependency chains in `via`), `unmodeled_db_columns`, and `missing_db_columns`.
71
+ - The `explores` and `join_analysis` lists identify reachable explores exposing the matched fields, alongside join diagnostics (`status`, `relationship`, `inferred_relationship`, `safety_warnings`, and `precedents`).
72
+ - The `tests`, `aggregate_tables`, `explore_queries`, and `usages` lists track existing LookML data tests, aggregate tables, Quick Start queries, and content validator `usages` in the dependency DAG.
73
+
74
+ ## Canonical Looker Query translation gate
75
+
76
+ Before considering any derived table (NDT or SDT), execute a preliminary check: attempt to translate the incoming SQL query into a Canonical Looker Query based on [How Looker Generates SQL](https://docs.cloud.google.com/looker/docs/how-looker-generates-sql):
77
+
78
+ ```sql
79
+ SELECT <dims>, <agg_measures>
80
+ FROM <base>
81
+ LEFT JOIN <joins>
82
+ WHERE <filters>
83
+ GROUP BY <dims>
84
+ HAVING <measure_filters>
85
+ ORDER BY ...
86
+ LIMIT ...
87
+ ```
88
+
89
+ Looker generates this SQL shape deterministically:
90
+
91
+ 1. The base view sits in the `FROM` clause, and foreign tables join via `join:` declarations (`sql_on:` or `sql:`).
92
+ 2. Dimensions represent columns and scalar expressions grouped by `GROUP BY` and projected in `SELECT`.
93
+ 3. Measures represent aggregations (`SUM`, `COUNT`, `AVG`, and filtered measures) in `SELECT`.
94
+ 4. Dimension filters map to predicates in `WHERE` joined by `AND`.
95
+ 5. Measure filters map to predicates in `HAVING`.
96
+ 6. Sorting and paging map to `ORDER BY` (`sorts`) and `LIMIT`.
97
+
98
+ ### The canonical translation test
99
+
100
+ Check whether a SQL query (even one with multiple CTEs or subqueries) can be expressed in this canonical form without changing grain or requiring non-`PK = FK` joins by combining:
101
+
102
+ - Table selections from an existing Explore graph,
103
+ - Single-table dimension projections and filters in `bronze/:table.refinement.lkml`,
104
+ - Single-table measures in `bronze/:table.refinement.lkml` or cross-view dimensions and measures in `silver/:view_a_:view_b_fields.view.lkml` (bare-joined via `sql: ;;`),
105
+ - Standard runtime filters (`WHERE`) and post-aggregation Table Calculations or Custom Fields.
106
+
107
+ When those conditions hold, the query maps directly to an Explore `query:` Quick Start block (for the underlying modeled `dimensions`, `measures`, `filters`, `pivots`, `subtotals`, `limit`, `sorts`, and `timezone`) and a corresponding Looker `WriteQuery` or `run_inline_query` payload (which carries any Table Calculations or Custom Fields in `dynamic_fields`) with zero derived tables. Never place Table Calculations or Custom Fields (`dynamic_fields`, `table_calculations`, or `custom_fields`) inside an `explore: { query: {} }` LookML block, because Looker's `explore: { query: {} }` syntax does not support them:
108
+
109
+ ```lookml
110
+ # In silver/explores/orders.explore.lkml
111
+ explore: +orders {
112
+ query: net_revenue_by_country {
113
+ description: "Net revenue by country for active users"
114
+ dimensions: [users.country]
115
+ measures: [orders.order_count, orders.total_net_revenue]
116
+ filters: [orders.status: "-Cancelled", users.is_deleted: "no"]
117
+ sorts: [orders.total_net_revenue: desc]
118
+ limit: 500
119
+ }
120
+ }
121
+ ```
122
+
123
+ Corresponding Looker `WriteQuery` or MCP `run_inline_query` payload (where Table Calculations and Custom Fields live in `dynamic_fields`):
124
+
125
+ ```json
126
+ {
127
+ "model": "ecommerce",
128
+ "view": "orders",
129
+ "fields": [
130
+ "users.country",
131
+ "orders.order_count",
132
+ "orders.total_net_revenue"
133
+ ],
134
+ "filters": {
135
+ "orders.status": "-Cancelled",
136
+ "users.is_deleted": "no"
137
+ },
138
+ "sorts": ["orders.total_net_revenue desc"],
139
+ "limit": "500",
140
+ "dynamic_fields": "[{\"table_calculation\":\"pct_of_total\",\"label\":\"% of Total Revenue\",\"expression\":\"${orders.total_net_revenue} / sum(${orders.total_net_revenue})\"}]"
141
+ }
142
+ ```
143
+
144
+ Only when a query fails this canonical translation test (due to grain-changing reaggregations, dynamic window rankings, unpivot unions, or non-key joins) should the agent escalate to Silver NDTs or Gold specialty explores.
145
+
146
+ ## The Medallion LookML architecture (Bronze, Silver, and Gold)
147
+
148
+ Every LookML modification prescribed by the agent follows this three-layer directory structure:
149
+
150
+ ```text
151
+ ├── bronze/
152
+ │ ├── order_items.view.lkml # Autogenerated 1:1 physical table (DO NOT MODIFY)
153
+ │ ├── order_items.refinement.lkml # Created ONLY on-demand (PKs, single-table measures/dims, hidden/suggestable, descriptions)
154
+ │ ├── orders__line_items.view.lkml # Companion UNNEST or JSON extracted view (:table__:col.view.lkml)
155
+ │ ├── calendar_dates.view.lkml # Standard calendar spine view for date joins
156
+ │ ├── :schema/ # Used ONLY if table name collides across schemas
157
+ │ │ ├── users.view.lkml
158
+ │ │ └── users.refinement.lkml
159
+ │ ├── :dataset_or_database/:schema/ # Used ONLY if schema + table name collide across databases/projects
160
+ │ │ ├── events.view.lkml
161
+ │ │ └── events.refinement.lkml
162
+ │ └── explores/ # Hidden explores for NDT explore_source OR the 4 physical table exceptions
163
+ │ └── order_items_base.explore.lkml # MUST set hidden: yes or extension: required
164
+ ├── silver/
165
+ │ ├── inventory_items_order_items_fields.view.lkml # Cross-view DAG dimensions & measures (alphabetical view names + _fields, bare-joined)
166
+ │ ├── user_order_facts.view.lkml # Consolidated entity NDTs (ephemeral by default), ${EXTENDED} window views, UNION ALL SDTs
167
+ │ └── explores/ # Core fact-anchored Explores (90% of use cases)
168
+ │ └── orders.explore.lkml # Strictly PK=FK equality joins (default left_outer) + bare-joined *_fields; ZERO Liquid; Quick Start query: blocks
169
+ ├── gold/
170
+ │ ├── orders_dynamic_rank.view.lkml # Specialty views: bind_all_filters NDTs, Liquid SDTs, AS OF SDTs, OJOF COALESCE views
171
+ │ └── explores/ # Specialty Explores (extends: [silver_explore], tags: ["gold"])
172
+ │ └── orders_subscription_attribution.explore.lkml # Inequality joins, OJOF, Long Metrics, Liquid/bind_all_filters + always_filter, aggregate_table
173
+ ├── tests/
174
+ │ ├── bronze/
175
+ │ │ └── order_items_base.test.lkml # Single-table measures tested at table grain, grouped into 1 query, filtered to immutable data
176
+ │ └── silver/
177
+ │ └── orders.test.lkml # Silver metrics grouped by join path DAG branch, filtered to immutable data
178
+ └── project.model.lkml # Includes bronze/explores/*, silver/explores/*, gold/explores/*, and tests/**/*.test.lkml
179
+ ```
180
+
181
+ ### Bronze layer (`bronze/`, raw physical tables and single-table refinements)
182
+
183
+ The `bronze/:table.view.lkml` file is a 1:1 autogenerated physical table mapping that already includes Looker's autogenerated `measure: count { type: count }` and basic `dimension_group` blocks. Never hand-edit this file. If `lkr parse sql-lookml-compare` shows that a physical table in `parsed_query.tables` does not have a corresponding Bronze view in `views` (or has unmodeled physical database columns in `views[].unmodeled_db_columns`), invoke the [generate-view-from-table](../generate-view-from-table/SKILL.md) skill (`lkr tools lookml create --folder bronze`, `generate_lookml_with_new_files`, or MCP `generate_view_from_table` / `create_view_from_table`) to autogenerate `bronze/:table.view.lkml`, pull it locally, and re-run `lkr parse sql-lookml-compare`. Keep views flat in `bronze/` by default; move colliding table names to `bronze/:schema/:table.view.lkml`, and if `:schema.:table` also collides across databases or projects, move to `bronze/:dataset_or_database/:schema/:table.view.lkml`.
184
+
185
+ Bronze can hold dimensions and measures as long as their entire dependency DAG references only that single table. Self-contained single-table measures (`sum`, `average`, `count_distinct`, single-table filtered measures like `filters: [status: "Complete"]`, and single-table ratios like `1.0 * ${total_revenue} / NULLIF(${order_count}, 0)`) live in `bronze/:table.refinement.lkml`.
186
+
187
+ Place `bronze/:table.refinement.lkml` directly next to its base `.view.lkml`. Never create a refinement file automatically; it should exist on-demand only if non-generator dimension transformations, single-table measures, primary keys or composite PKs, `hidden: yes` and `suggestable: no` flags (for raw JSON columns), or descriptions (including partition min-date and clustering column notes) are being added. When created, its first line must be `include: ":table.view.lkml"`. Maintain `bronze/calendar_dates.view.lkml` as the standard calendar spine view in Bronze for date-spine and multi-date alignment joins.
188
+
189
+ Explores in `bronze/explores/:name.explore.lkml` must declare `hidden: yes` (or `extension: required` when extended by Silver explores) and only exist if actively referenced by an `explore_source:` in a Silver or Gold Native Derived Table, with four physical table exceptions:
190
+
191
+ 1. `UNNEST` joins for `REPEATED` columns (`explore: table_base { extension: required view_name: table join: table__repeated_col { sql: LEFT JOIN UNNEST(${table.repeated_col}) AS table__repeated_col WITH OFFSET AS table__repeated_col_offset ;; relationship: one_to_many } }`) with `WITH OFFSET` composite PKs. Inline `1:1` scalar subqueries in refinements must reference the LookML `${dimension}`, never `${TABLE}.?`.
192
+ 2. Bare joins (`sql: ;;`) for companion JSON extracted views (`explore: table_base { extension: required view_name: table join: table__metadata { relationship: one_to_one sql: ;; } }`) paired with `bronze/:table__:col.view.lkml`.
193
+ 3. Wildcard table base explores (`explore: events_base { extension: required view_name: events ... }`) with `_TABLE_SUFFIX` physical shard pruning (`always_filter:` and `sql_always_where:` using Liquid `date_start`, `date_end`, and `condition` tags). Wildcard table pruning is physical storage management that protects the warehouse from accidental full scans, making its Liquid the only sanctioned exception to the rule against Liquid in Bronze.
194
+ 4. Datetime-partitioned table base explores (`explore: table_base { extension: required view_name: table always_filter: { filters: [table.event_date: "7 days"] } }`) on the actual column `dimension_group` to prevent unconstrained scans and warehouse `require_partition_filter` errors (no redundant `_PARTITIONDATE` pseudo-dimensions when partitioned on an actual column, and no `conditionally_filter:`).
195
+
196
+ In all four cases, downstream Silver explores inherit these physical structures and protections via `extends: [base]`.
197
+
198
+ ### Silver layer (`silver/`, core semantic layer and cross-view `*_fields` views)
199
+
200
+ Any dimension or measure that anywhere in its dependency DAG references different views belongs in its own dedicated Silver view named by sorting the referenced view names alphabetically and appending `_fields` (`silver/:view_a_:view_b_fields.view.lkml`), and is bare-joined (`sql: ;;`, `relationship: one_to_one`) into the Explore that joins those views together. For example, if `order_items.gross_margin` is `${order_items.sale_price} - ${inventory_items.cost}` and `order_items.total_gross_margin` sums `${order_items.gross_margin}`, move both `gross_margin` and `total_gross_margin` (`type: sum`, `sql: ${gross_margin} ;;`) to `silver/inventory_items_order_items_fields.view.lkml` and bare-join `inventory_items_order_items_fields` into the Explore that joins `order_items` and `inventory_items`.
201
+
202
+ Derived views in `silver/:name.view.lkml` cover four additional patterns:
203
+
204
+ 1. Consolidated entity-grain NDTs (`explore_source:`) combine multiple `GROUP BY entity_id` rollups into a single cohesive NDT (such as `silver/user_order_facts.view.lkml`). Silver NDTs are ephemeral by default (no premature `datagroup_trigger:` persistence), and `derived_column:` in an NDT runs after `GROUP BY` for post-aggregation entity ranking and windows.
205
+ 2. Pre-`GROUP BY` row-level window views (`extends: [base_view]` with `${EXTENDED}`) compute row-level windows before `GROUP BY` (such as `LAG(created_at)` between consecutive orders) using `SELECT ${EXTENDED}.*, ... FROM ${EXTENDED}`.
206
+ 3. Sessionization, windowing, and `QUALIFY` deduplication views group raw events into sessions or deduplicate single-table CDC ingestion logs (`QUALIFY ROW_NUMBER() = 1`).
207
+ 4. `UNION ALL` SQL Derived Tables (SDTs) combine tables with overlapping schemas or the same table twice using `extends: [base_view]`, `fields: [ALL_FIELDS*, -view.unneeded]`, a mandatory compound primary key, and `${}` substitution on every table and column.
208
+
209
+ Explores in `silver/explores/:name.explore.lkml` are anchored exclusively on fact, event, or transaction tables in the `FROM` clause (`orders`, `order_items`, `events`). Dimensional and lookup tables (`products`, `users`) are joined into fact Silver Explores rather than getting standalone Silver Explores. Every table join in a Silver explore must be a primary-key-to-foreign-key equality relationship (`many_to_one`, `one_to_one`, `one_to_many`), defaulting SQL `INNER JOIN` to `type: left_outer` unless a metric strictly requires inner filtering, plus bare joins (`sql: ;;`, `relationship: one_to_one`) for cross-view `*_fields.view.lkml` views. Never include Liquid (`{% condition %}`, `{% parameter %}`), `bind_all_filters: yes`, inequality or time-bounded joins (`BETWEEN`, `>=`), non-key dimension joins, or `FULL OUTER JOIN ON FALSE` in Silver. Always declare `view_name:` explicitly on the Silver explore so Gold explores can extend it. When adding Quick Start `query:` blocks on the Silver Explore, include only modeled `dimensions`, `measures`, `filters`, `pivots`, `subtotals`, `limit`, `sorts`, `timezone`, and `description` (never Table Calculations or Custom Fields, which live in the query payload or dashboard tile).
210
+
211
+ ### Gold layer (`gold/`, specialty and advanced analytical patterns)
212
+
213
+ Views in `gold/:name.view.lkml` handle dynamic query context and specialty SQL constructs: dynamic `bind_all_filters: yes` NDTs, raw `sql:` Derived Tables (SDTs) using `{% condition %}` or `{% parameter %}`, point-in-time `AS OF` attribution deduplication, coalesced field-only views (`gold/:dims.view.lkml`) for `FULL OUTER JOIN ON FALSE` (OJOF), and Long Metric unpivot views.
214
+
215
+ Whenever a SQL Derived Table (`derived_table: { sql: ... ;; }`) is written in Silver or Gold, every table and field reference must use LookML substitution syntax `${...}`: `${view_name.SQL_TABLE_NAME} AS view_name` (or `${EXTENDED}` when extending a base view; never raw `db.schema.table`) for every table in `FROM`, `JOIN`, or `UNION ALL`, and `${view_name.field_name}`, `${TABLE}.column_name`, or `${field_name}` for every referenced column. This guarantees Looker's dependency graph and `lkr parse sql-lookml-compare` (`via` lineage) track all references.
216
+
217
+ Explores in `gold/explores/:name.explore.lkml` extend a Silver explore (`extends: [silver_explore]` with explicit `view_name:`) and carry `tags: ["gold"]`. Gold is strictly a folder structure (`gold/` and `gold/explores/`) and a LookML tag (`tags: ["gold"]`), never an explore name suffix (use `explore: orders_subscription_attribution`, never `orders_gold`). Gold explores isolate inequality and SCD Type 2 joins, OJOF (`FULL OUTER JOIN ON FALSE` plus a `COALESCE` view joined via `sql: ;;`), Long Metrics, dynamic `bind_all_filters: yes` or templated filters and parameters (`{% condition %}`, `{% parameter %}`) paired with mandatory `always_filter:` (never `conditionally_filter:`), and `aggregate_table` rollups.
218
+
219
+ ## Standardized agent diagnostic output contract
220
+
221
+ When the agent analyzes a query via `lkr parse sql-lookml-compare`, it outputs its diagnosis using this structure:
222
+
223
+ ```markdown
224
+ ### SQL pattern match: [Pattern name]
225
+ - Detected SQL signature: [Specific AST elements from `parsed_query`: e.g., `ctes`, `joins`, `columns[].is_agg`, `where`]
226
+ - LookML mapping and lineage:
227
+ - Matched views: [view_name](file:///path/to/bronze/view.view.lkml)
228
+ - Matched fields: [field_name](file:///path/to/bronze/view.refinement.lkml) (via: `...`)
229
+ - Unmodeled columns: `[col_a, col_b]`
230
+ - Affected explores and tests: `[explore_a]`, `[test_b]`
231
+ - Target layer: `Query Layer (Table Calc / Custom Field)` | `Bronze Refinement` | `Silver (*_fields / NDT / PK-FK Explore)` | `Gold (Specialty Explore / View)`
232
+ - Impact: [Why naive translation fails: fanout risk, partition scan, measure sprawl, or duplicate maintenance]
233
+ - How to handle (prescribed action):
234
+ - Autonomy tier: `Tier 1 (Auto-Apply)` | `Tier 2 (Batch Confirm)` | `Tier 3 (Architectural Escalation)`
235
+ - Proposed diff: [Exact surgical LookML block or Query/Dashboard custom field config]
236
+ ```
237
+
238
+ ## Operational summary tracking (`./data/:id/summary.md`)
239
+
240
+ Throughout every `agentic-lookml` session, maintain a high-level operations log in `./data/:id/summary.md`. Update `summary.md` incrementally as each phase completes or self-heals so reviewers have a clear audit trail attached to the GitHub pull request:
241
+
242
+ 1. Record the physical tables extracted from the input SQL and the `bronze/:table.view.lkml` base views generated or refreshed via `generate-view-from-table`.
243
+ 2. Document how each input SQL query deconstructs into the Canonical Looker Query form (`FROM` base table, `LEFT JOIN` paths, projected dimensions, aggregated measures, `WHERE` and `HAVING` filters, `ORDER BY`, `LIMIT`) or why it escalates to a derived table.
244
+ 3. List the detected pattern categories from the 14 SQL-to-LookML playbooks (for example, single-table with union `["simple_single_table", "unions"]`, or `["pk_fk_joins", "conditional_measures_and_bucketing"]`) and their AST triggers.
245
+ 4. Log every LookML file authored or modified across Bronze refinements (`bronze/:table.refinement.lkml`), hidden base explores (`bronze/explores/:table_base.explore.lkml`), Silver cross-view fields, NDTs, SDTs, and explores (`silver/`), and Gold specialty views and explores (`gold/`).
246
+ 5. Record the outcome of the pre-modeling gate and final LookML validator and SQL-to-LookML parity checks. Whenever validation or query parity fails, log `Validation failed:` with the compiler error, missing reference, or row mismatch, followed by `Updating LookML:` describing the fix applied before re-validating.
247
+ 6. Log the LookML data tests authored in `tests/bronze/:table_base.test.lkml` and `tests/silver/:explore.test.lkml`, the immutable filter slice chosen (such as `created_date = "2026-01-01"` or a primary key fallback), the frozen baseline values from `code-mode`, and the targeted `run_lookml_test` execution result.
248
+
249
+ Use this structure for `./data/:id/summary.md`:
250
+
251
+ ```markdown
252
+ # Agentic LookML operations summary (`<run_id>`)
253
+
254
+ ## Generating base views
255
+ - Generated and refreshed `bronze/orders.view.lkml` from `public.orders`
256
+ - Generated and refreshed `bronze/archived_orders.view.lkml` from `public.archived_orders`
257
+
258
+ ## Deconstructing query to canonical form
259
+ - Query 1 deconstructs CTE and `UNION ALL` branches into canonical base view `orders_all`, grouping by `status` and `created_date` with measures `order_count` and `total_net_revenue`.
260
+
261
+ ## Query patterns from original query
262
+ - Query 1 matched `simple_single_table` and `unions` (single-table aggregation combined with `UNION ALL` across active and archived tables).
263
+
264
+ ## Writing LookML (Bronze)
265
+ - Created `bronze/orders.refinement.lkml` (primary key `id`, measure `total_net_revenue`)
266
+ - Created `bronze/explores/orders_base.explore.lkml` (`hidden: yes`)
267
+
268
+ ## Pre-modeling validation and parity gate
269
+ - Initialized SQL Runner query (`/sql/<sql_id>`)
270
+ - Validation failed: `Unknown field "orders.net_amount" referenced in bronze/orders.refinement.lkml`
271
+ - Updating LookML: Replaced `${net_amount}` with `${amount}` in `bronze/orders.refinement.lkml`
272
+ - Validation passed: LookML Validator passed with 0 errors and early Bronze query parity matched.
273
+
274
+ ## Writing LookML (Silver and Gold)
275
+ - Created `silver/orders_all.view.lkml` (`UNION ALL` SDT extending `orders` with compound PK)
276
+ - Created `silver/explores/orders.explore.lkml` (fact-anchored explore with Quick Start `query:` block)
277
+
278
+ ## Writing and evaluating tests
279
+ - Created `tests/bronze/orders_base.test.lkml` (grouped refinement measures filtered to immutable date `orders.created_date = "2026-01-01"`)
280
+ - Created `tests/silver/orders.test.lkml` (grouped by join path DAG branch at `orders.created_date = "2026-01-01"`)
281
+ - Targeted test execution via `run_lookml_test` passed and final SQL-to-LookML parity matched (`/x/<query_id>`).
282
+ ```
283
+
284
+ ## Triage between Table Calculations, Custom Fields, and LookML
285
+
286
+ Before writing LookML, check whether the SQL expression belongs in the LookML semantic model or in the query and dashboard layer. In `sql-lookml-compare`, this applies when all underlying physical columns referenced by the SQL expression are already modeled in `views[].fields` (with no `unmodeled_db_columns` for those columns), and the SQL expression is a filtered aggregation, a `CASE WHEN` bucketing expression, a ratio of two aggregations, or a cross-row calculation. Note that Custom Fields and Table Calculations cannot be placed inside an `explore: { query: {} }` Quick Start block; they belong in the Looker API query payload (`dynamic_fields`), a Saved Look, or a LookML Dashboard tile.
287
+
288
+ | Calculation type | Keep in Query / Dashboard when... | Promote to LookML (`bronze/*.refinement.lkml` for single-table DAG, `silver/*_fields.view.lkml` for multi-view DAG) when... |
289
+ | :--- | :--- | :--- |
290
+ | Filtered measure (`SUM(CASE WHEN country = 'US' THEN rev END)`) | Prefer a Custom Measure using [Looker filter expressions](https://docs.cloud.google.com/looker/docs/filter-expressions) or [`matches_filter(${view.field}, \`expr\`)`](https://docs.cloud.google.com/looker/docs/matches-filter) when the slice (including an ad-hoc cross-column `OR` condition) is used infrequently on a single tile. Avoids polluting the view with $N \times M$ country and status measures. | The filtered metric is a canonical, organization-wide KPI reused across many dashboards and explores (such as single-table `net_completed_revenue` in `bronze/:table.refinement.lkml`, or cross-view filtered KPIs in `silver/:view_a_:view_b_fields.view.lkml` bare-joined into the Explore). |
291
+ | Row-level bucketing / expression (`CASE WHEN ...`, string/math expressions) | Use a Custom Dimension when the logic is specific to a single ad-hoc report or campaign (such as `CASE WHEN promo_id IN (12, 45) THEN 'Target' ELSE 'Control' END`). | The logic is simple and reusable for other users (standard age tiers, `is_enterprise`, clean status category) or references an unmodeled `${TABLE}.col`; promote single-table dimensions to `bronze/:table.refinement.lkml` and cross-view dimensions (`${order_items.sale_price} - ${inventory_items.cost}`) to `silver/:view_a_:view_b_fields.view.lkml`. |
292
+ | Measure-to-measure math and visual offsets (`SUM(a)/SUM(b)`, `% of total`, `LAG`, running total) | Prefer a Table Calculation in the query or dashboard layer (optionally paired with a Quick Start `query:` block on the Silver Explore for the underlying modeled fields) for post-aggregation visual math (`${measure_a} / nullif(${measure_b}, 0)`, `${measure} / sum(${measure})`, `running_total()`, `offset()`). | A ratio is a foundational business metric (such as single-table `average_order_value` in `bronze/:table.refinement.lkml`, or cross-table `revenue_per_user` in `silver/order_items_users_fields.view.lkml`) that users sort by, drill into, or reuse in NDTs. |
293
+
294
+ ## Core pattern rules summary
295
+
296
+ ### Scalability of approach and blast-radius triage
297
+
298
+ Inspect `views[].fields`, `views[].unmodeled_db_columns`, `views[].missing_db_columns`, `views[].fields[].via`, `join_analysis`, and `queries[].tests`, then route into one of three autonomy tiers:
299
+
300
+ 1. Tier 1 (autonomous auto-apply) applies when the query maps cleanly to existing Bronze and Silver views, `tests` are unaffected, and gaps consist only of missing or out-of-date Bronze views, reusable single-table dimensions, durations, JSON companion extractions, inline `1:1` `UNNEST` dimensions, or core single-table measures (`SUM` or `COUNT`). If a Bronze view does not exist yet in `views` or is missing a physical column in `views[].unmodeled_db_columns`, use the [generate-view-from-table](../generate-view-from-table/SKILL.md) skill (`lkr tools lookml create --folder bronze`, `generate_lookml_with_new_files`, or MCP `generate_view_from_table`) to autogenerate or sync `bronze/:table.view.lkml` (never hand-edit it); for single-table calculated dimensions, durations, or single-table measures, create or append to `bronze/:table.refinement.lkml` (on-demand only).
301
+ 2. Tier 2 (batch proposal) applies when the query requires adding a new `PK = FK` join or bare-joined `silver/:view_a_:view_b_fields.view.lkml` view to a fact-anchored Silver Explore, creating a Bronze base explore for one of the four physical table exceptions (`UNNEST`, bare JSON join, wildcard `_TABLE_SUFFIX` pruning, or datetime partition `always_filter:`), creating a consolidated Silver NDT, or triaging Custom Measures versus LookML Measures. Present a concise batch table of proposed Bronze and Silver updates.
302
+ 3. Tier 3 (architectural escalation / plan first) applies when the query requires spinning up a Gold Specialty Explore (`tags: ["gold"]` with `bind_all_filters: yes`, Liquid `{% condition %}`, inequality or time-bound joins, OJOF `FULL OUTER JOIN ON FALSE`, or `aggregate_table`), or editing a dimension with downstream `via` dependents or `tests`. Present the blast radius (`via` lineage plus `tests`) and confirm the Gold design with the user.
303
+
304
+ ### Fixed conditional measures versus runtime filters and Custom Measures
305
+
306
+ 1. If the query only computes metrics for a single slice (or repeats the same condition across all metrics), strip the condition from the measure. Ensure the base measure (`total_amount`) and filter dimensions (`status`, `country`) exist in `bronze/:table.refinement.lkml`, and apply the slice as a runtime Explore or Dashboard filter.
307
+ 2. For infrequent multi-slice comparisons or ad-hoc cross-column `OR` conditions in a single report (such as `US Orders` versus `UK Orders`), do not add permanent LookML measures to `.refinement.lkml`. Keep `orders.count` in LookML and define the slice as a Custom Measure using [Looker filter expressions](https://docs.cloud.google.com/looker/docs/filter-expressions) or Looker's [`matches_filter` syntax](https://docs.cloud.google.com/looker/docs/matches-filter) (`matches_filter(${orders.country}, \`US\`)` or `matches_filter(${orders.status}, \`Complete\`) OR ${orders.is_override}`) on the dashboard tile or Look query.
308
+ 3. If a conditional measure is a core single-table domain KPI (requiring no joins across its entire dependency DAG, such as `completed_revenue`), define it in `bronze/:table.refinement.lkml` using Looker's declarative `filters:` syntax (never raw SQL `CASE WHEN` inside `sql:`). If a canonical single-table KPI requires `OR` across two columns, define a hidden `type: yesno` dimension in `bronze/:table.refinement.lkml` and filter on `filters: [is_cond: "yes"]`:
309
+ ```lookml
310
+ # In bronze/orders.refinement.lkml
311
+ include: "orders.view.lkml"
312
+ view: +orders {
313
+ measure: completed_revenue {
314
+ type: sum
315
+ sql: ${amount} ;;
316
+ filters: [status: "Complete"]
317
+ value_format_name: usd
318
+ }
319
+ }
320
+ ```
321
+ 4. If any dimension, filtered measure, or ratio anywhere in its dependency DAG references fields from multiple joined views (such as `gross_margin` defined as `${order_items.sale_price} - ${inventory_items.cost}` and `total_gross_margin` defined as `type: sum` of `${gross_margin}`, or `${orders.gross_revenue} / NULLIF(${users.count}, 0)`), define all fields in that cross-view DAG inside a dedicated Silver view named alphabetically with `_fields` (`silver/inventory_items_order_items_fields.view.lkml` or `silver/orders_users_fields.view.lkml`) and bare-join it (`sql: ;;`, `relationship: one_to_one`) into the Explore that joins those views.
322
+
323
+ ### Join paths, primary keys, fanouts, and layer routing
324
+
325
+ 1. Bronze Explores (`bronze/explores/*.explore.lkml`) hold physical intra-table joins only: `UNNEST` joins on `REPEATED` columns (`sql: LEFT JOIN UNNEST(${table.array_col}) AS table__array_col WITH OFFSET AS table__array_col_offset ;;`, `relationship: one_to_many`, paired with a `WITH OFFSET` composite PK in `bronze/:table__:array_col.view.lkml`) and bare joins (`sql: ;;`, `relationship: one_to_one`) for companion JSON views (`bronze/:table__:json_col.view.lkml`). Inline `1:1` array lookups live as scalar dimensions in `bronze/:table.refinement.lkml` referencing LookML `${array_col}`, never `${TABLE}.?`.
326
+ 2. Silver Explores (`silver/explores/*.explore.lkml`) are created only for `FROM`-clause fact or event tables (never standalone lookup or dimension tables like `users` or `products`) and contain strictly `PK = FK` equality joins (`sql_on: ${orders.user_id} = ${users.id} ;;`), defaulting SQL `INNER JOIN` to `type: left_outer` (`many_to_one`, `one_to_one`, `one_to_many`), plus bare joins (`sql: ;;`, `relationship: one_to_one`) for `silver/:view_a_:view_b_fields.view.lkml` cross-view field views. For date alignment, join `bronze/calendar_dates.view.lkml`.
327
+ 3. Gold Explores (`gold/explores/*.explore.lkml`, `extends: [silver_explore]`, `tags: ["gold"]`) hold all inequality and time-bounded joins (`BETWEEN`, `>=`, `<`, SCD2, point-in-time `AS OF` deduplication SDTs) and OJOF (`FULL OUTER JOIN ON FALSE` plus `COALESCE` view) joins for unrelated fact schemas.
328
+ 4. Ensure every joined view has a verified primary key (`primary_key: yes` in `bronze/:table.refinement.lkml`), and run the Two-Table Cardinality Audit SQL in [03-pk-fk-joins.md](references/03-pk-fk-joins.md) via `lkr code-mode` whenever a join's target view lacks a verified PK or relationship.
329
+
330
+ ### Nested CTEs, reaggregations, windows, unions, and Liquid isolation
331
+
332
+ - Walk CTE chains from leaf to root ([06-nested-ctes-and-subqueries.md](references/06-nested-ctes-and-subqueries.md)): flatten pass-through projections, filters, and `PK = FK` joins into Bronze refinements, Silver `*_fields` views, and Silver Explores with zero derived tables; consolidate static entity-grain rollups into ephemeral Silver NDTs ([07-measure-of-measures-reaggregation.md](references/07-measure-of-measures-reaggregation.md)); and isolate filter-dependent CTEs in Gold (`bind_all_filters: yes` or `{% condition %}` paired with `always_filter:`).
333
+ - Route window functions and `QUALIFY` ([08-window-functions-and-qualify.md](references/08-window-functions-and-qualify.md)) across query-layer Table Calculations (display-set windows), Silver NDT `derived_column:` (post-`GROUP BY` windows), Silver `extends: [base_view]` with `${EXTENDED}` (pre-`GROUP BY` row-level windows or single-table CDC `QUALIFY`), and Gold `bind_all_filters: yes` NDTs or `AS OF` SDTs.
334
+ - Route `UNION ALL` and multi-fact stitching ([09-unions.md](references/09-unions.md), [05-dimensional-non-key-joins.md](references/05-dimensional-non-key-joins.md), [12-table-wildcards.md](references/12-table-wildcards.md)) across Bronze wildcard `_TABLE_SUFFIX` base explores (the sole Liquid exception outside Gold), Silver `UNION ALL` SDTs (`${}` substitution on every table and column, `extends: [base_view]`, mandatory compound PK, and `fields: [ALL_FIELDS*, -view.unmapped_col]`), and Gold OJOF (`FULL OUTER JOIN ON FALSE` + `COALESCE` view) or Long Metrics explores.
335
+ - Enforce partition pruning and Liquid isolation ([13-large-tables-and-partitioning.md](references/13-large-tables-and-partitioning.md), [14-templated-filters-and-parameters.md](references/14-templated-filters-and-parameters.md)): model off actual partition columns (never redundant `_PARTITIONDATE` pseudo-dimensions when partitioned on a real column), document partition min-dates and clustering columns in `bronze/:table.refinement.lkml`, enforce `always_filter:` in `bronze/explores/:table_base.explore.lkml`, and keep `aggregate_table:`, `{% condition %}`, `{% parameter %}`, and `bind_all_filters: yes` strictly in `gold/` paired with `always_filter:`.
336
+
337
+ ## Opinionated LookML testing and evaluation (`tests/`)
338
+
339
+ LookML tests (`test: [name] { explore_source: ... assert: ... }`) run in the Looker compiler and warehouse via `run_lookml_test` to verify that semantic models, joins, and calculations produce the expected numbers without regressions. Follow these rules whenever adding or refactoring LookML.
340
+
341
+ ### Pre-modeling and pre-test verification gate
342
+
343
+ Execute the LookML validator and query parity checks immediately after generating Bronze views and refinements, before writing any downstream LookML tests and before completing Silver modeling. Once `bronze/:table.view.lkml`, `bronze/:table.refinement.lkml`, and `bronze/explores/:table_base.explore.lkml` are generated:
344
+
345
+ 1. Push the Bronze files to Looker development mode (`lkr --dev tools lookml push --no-reset`).
346
+ 2. Run [validate_and_commit_branch.py](../agentic-lookml-workflow/scripts/validate_and_commit_branch.py) with `--var skip_tests="true"` (omitting `commit_message`) via `lkr --dev code-mode sandbox --dev-mode`. If syntax errors, broken references, or explore definition bugs exist, resolve them before writing any tests.
347
+ 3. For every query in `input.sql`, create a SQL Runner query using [init_sql_queries.py](../agentic-lookml-workflow/scripts/init_sql_queries.py) to capture the SQL query ID and interactive SQL Runner URL (`/sql/:sql_id`).
348
+ 4. Formulate the initial Looker explore query corresponding to the SQL query and run [record_query_parity.py](../agentic-lookml-workflow/scripts/record_query_parity.py) to confirm that Looker's generated query returns the same rows as SQL Runner.
349
+ 5. Never author LookML data tests on top of a model that fails validation or diverges from the user's SQL query results. Both checks must pass before writing tests and finalizing Silver modeling.
350
+
351
+ ### Silver layer testing rule
352
+
353
+ Every metric in any Silver layer must have a Looker test at an appropriate grain:
354
+ - Fact-level measures on base fact views (`orders.total_net_revenue`, `orders.order_count`), Silver NDT rollups (`user_order_facts.lifetime_orders`), and cross-view dimensions and measures in `silver/:view_a_:view_b_fields.view.lkml` (`inventory_items_order_items_fields.total_gross_margin`) must have corresponding test assertions in `tests/silver/:explore.test.lkml`.
355
+ - Untested metrics in the Silver layer are considered incomplete.
356
+
357
+ ### Bronze refinement testing rule
358
+
359
+ Every measure added to a Bronze refinement must be tested, and all measures for a given refinement must be grouped into a single test query:
360
+ - Test all self-contained single-table measures (`type: sum`, `type: count`, `type: average`, filtered single-table KPIs) defined in `bronze/:table.refinement.lkml`.
361
+ - Rather than creating individual single-measure test queries, group all measures for that Bronze table into a single `explore_source` test block with multiple `column:` definitions and multiple `assert:` statements.
362
+ - To test Bronze refinement measures without polluting public explores or colliding with Silver explore names, create a hidden base explore:
363
+ ```lookml
364
+ # In bronze/explores/order_items_base.explore.lkml
365
+ explore: order_items_base {
366
+ label: "Order Items (Base)"
367
+ view_name: order_items
368
+ hidden: yes
369
+ }
370
+ ```
371
+ Use the `:table_base` naming convention with `hidden: yes` to avoid any namespace collision with `silver/explores/order_items.explore.lkml`.
372
+
373
+ ### Filtering to immutable data
374
+
375
+ Tests that query volatile data (such as "today", "yesterday", or unconstrained table scans) break over time as underlying rows change. Filter every test to an immutable slice:
376
+ - First, inspect the view's dimensions for immutable date or timestamp columns by matching name patterns such as `*_created_*`, `*_event_*`, `*_ordered_*`, `*_inserted_*`, `*_occurred_*`, `*_at`, or `*_date` (for example, `orders.created_date`), and filter to a fixed historical calendar date with stable data (such as `2026-01-01`).
377
+ - Second, if no immutable timestamp or date dimension exists on the table, filter by a specific primary key (such as `id = 123`).
378
+ - During test authoring, query the instance via `code-mode` (`run_inline_query` or `run_query`) on the chosen immutable slice (such as `created_date = '2026-01-01'`) to retrieve the exact calculated metric value, then freeze that number in the LookML `assert:` expression (for example, `${order_items.total_sale_price} = 125.50`).
379
+
380
+ Example Bronze test:
381
+ ```lookml
382
+ # In tests/bronze/order_items_base.test.lkml
383
+ test: order_items_base_refinement_metrics {
384
+ explore_source: order_items_base {
385
+ column: total_sale_price { field: order_items.total_sale_price }
386
+ column: order_item_count { field: order_items.count }
387
+ filters: {
388
+ field: order_items.created_date
389
+ value: "2026-01-01"
390
+ }
391
+ }
392
+ assert: total_sale_price_matches {
393
+ expression: ${order_items.total_sale_price} = 4520.25 ;;
394
+ }
395
+ assert: order_item_count_matches {
396
+ expression: ${order_items.count} = 84 ;;
397
+ }
398
+ }
399
+ ```
400
+
401
+ ### Grouping Silver layer tests by join path DAG branch
402
+
403
+ In Silver explores, foreign tables and bare-joined cross-view `*_fields` views join along explicit paths in the explore DAG (`orders -> users`, `orders -> order_items -> inventory_items`). Group all measures that depend on the same join path branch into a single `explore_source` test block. For example, in `orders.explore.lkml`:
404
+ 1. On the base fact branch, test `orders` measures (`order_count`, `total_revenue`) filtered to the immutable date.
405
+ 2. On the `orders -> users` join branch, test `users` measures joined to `orders` alongside `orders_users_fields` metrics (`revenue_per_user`) in one test block.
406
+ 3. On the `orders -> order_items -> inventory_items` join branch, test `inventory_items` and `inventory_items_order_items_fields` metrics (`total_gross_margin`) in one test block.
407
+
408
+ Example Silver test:
409
+ ```lookml
410
+ # In tests/silver/orders.test.lkml
411
+ test: orders_fact_metrics_at_fixed_date {
412
+ explore_source: orders {
413
+ column: order_count { field: orders.order_count }
414
+ column: total_net_revenue { field: orders.total_net_revenue }
415
+ filters: {
416
+ field: orders.created_date
417
+ value: "2026-01-01"
418
+ }
419
+ }
420
+ assert: order_count_matches {
421
+ expression: ${orders.order_count} = 42 ;;
422
+ }
423
+ assert: total_net_revenue_matches {
424
+ expression: ${orders.total_net_revenue} = 3850.00 ;;
425
+ }
426
+ }
427
+
428
+ test: orders_users_join_path_metrics {
429
+ explore_source: orders {
430
+ column: user_count { field: users.count }
431
+ column: revenue_per_user { field: orders_users_fields.revenue_per_user }
432
+ filters: {
433
+ field: orders.created_date
434
+ value: "2026-01-01"
435
+ }
436
+ }
437
+ assert: user_count_matches {
438
+ expression: ${users.count} = 38 ;;
439
+ }
440
+ assert: revenue_per_user_matches {
441
+ expression: round(${orders_users_fields.revenue_per_user}, 2) = 101.32 ;;
442
+ }
443
+ }
444
+ ```
445
+
446
+ ### File structure, model inclusion, and targeted test execution
447
+
448
+ - Partition tests by Medallion layer in `tests/`:
449
+ - `tests/bronze/:table_base.test.lkml`
450
+ - `tests/silver/:explore.test.lkml`
451
+ - Include all tests in `project.model.lkml`:
452
+ ```lookml
453
+ include: "tests/**/*.test.lkml"
454
+ ```
455
+ - Target every new or additional test with `lkr code-mode` before committing and pushing to the pull request. Looker's `run_lookml_test(project_id, file_id=None, test=None, model=None)` endpoint (inspectable via `lkr code-mode sandbox --code="return lookup('run_lookml_test')"` or `help('lookml_test')`) filters test execution by `file_id`, `test`, and `model`.
456
+ - After pushing new or modified `.test.lkml` files to the developer branch (`lkr --dev tools lookml push --no-reset`), target each new or additional test directly via `lkr code-mode sandbox --dev-mode` before committing or opening the pull request:
457
+ ```bash
458
+ lkr --base-url="$BASE_URL" --dev code-mode sandbox --dev-mode \
459
+ --code='return run_lookml_test(project_id="my_project", file_id="tests/silver/orders.test.lkml", test="orders_fact_metrics_at_fixed_date")'
460
+ ```
461
+ - Pass `--var file_ids="..."` and `--var test_names="..."` when running `validate_and_commit_branch.py` so only the targeted tests are evaluated before committing. Commit is blocked if any targeted test fails.
@@ -0,0 +1,76 @@
1
+ # Simple single-table queries (`simple_single_table`)
2
+
3
+ This category (`simple_single_table`) covers single-table queries that map to `bronze/:table.view.lkml` (autogenerated) and on-demand `bronze/:table.refinement.lkml` files for dimensions, primary keys, and single-table measures, exposed through fact-anchored `silver/explores/:fact.explore.lkml` files. Any dimension or measure that anywhere in its dependency DAG references multiple views belongs in its own dedicated `silver/:view_a_:view_b_fields.view.lkml` file and is bare-joined (`sql: ;;`) into the Explore. Queries in this category qualify for Tier 1 auto-application.
4
+
5
+ ## Tool categorization rule (`lkr parse`)
6
+
7
+ The parser tags a query with `simple_single_table` when `parsed_query` meets all of these conditions:
8
+
9
+ - `len(parsed_query.tables) == 1` (a single physical table).
10
+ - `parsed_query.joins == []` (no `JOIN` or `UNNEST` joins).
11
+ - `parsed_query.ctes == []` and no nested `FROM (SELECT ...)` subqueries.
12
+ - `parsed_query.statement_type == "SELECT"` (no `UNION`).
13
+ - Every `col` in `parsed_query.columns` has `col.sql` containing only direct columns, simple scalar functions (`DATE_TRUNC`, `COALESCE`), or standard single-pass aggregations (`col.is_agg == True` with `COUNT`, `COUNT(DISTINCT ...)`, `SUM`, `AVG`, `MIN`, `MAX`, and no nested `CASE` or window functions).
14
+
15
+ ## Representative SQL
16
+
17
+ ```sql
18
+ SELECT
19
+ status,
20
+ DATE_TRUNC(created_at, MONTH) AS created_month,
21
+ COUNT(*) AS row_count,
22
+ SUM(sale_price) AS total_revenue
23
+ FROM `project.dataset.orders`
24
+ WHERE created_at >= '2026-01-01'
25
+ GROUP BY 1, 2
26
+ ```
27
+
28
+ ## Impact of naive translation
29
+
30
+ 1. Modifying `bronze/orders.view.lkml` directly gets overwritten on the next schema sync because base Bronze views are autogenerated.
31
+ 2. Putting cross-view dimensions or measures in Bronze (or inside a single table's view refinement) breaks layer boundaries and view portability. Bronze can hold dimensions and measures only when their entire dependency DAG references that single table; any dimension or measure that anywhere in its DAG references multiple views belongs in its own `silver/:view_a_:view_b_fields.view.lkml` view and is bare-joined into the Explore.
32
+ 3. Creating `bronze/:table.refinement.lkml` when no non-generator transformations, single-table measures, primary keys, or descriptions are being added clutters `bronze/` with empty refinement files.
33
+ 4. Creating standalone Silver Explores for lookup or dimension tables (`products`, `users`, `locations`) when they are already joined into fact Explores (`order_items`, `orders`, `events`) fragments the user experience.
34
+
35
+ ## How to handle
36
+
37
+ ### Bronze base view (`bronze/:table.view.lkml`)
38
+
39
+ Inspect `queries[].parsed_query.tables` and `queries[].views` in `lkr parse sql-lookml-compare`. Compare each physical table in `parsed_query.tables` against `views`, and compare the SQL columns extracted from the query against `views[].fields` and `views[].unmodeled_db_columns`.
40
+
41
+ Looker's generator automatically outputs 1:1 physical dimensions, `dimension_group` blocks for timestamps and dates, and the default `measure: count { type: count }`. If `bronze/:table.view.lkml` does not exist yet in `views` or if physical columns are missing in `views[].unmodeled_db_columns`, follow the [generate-view-from-table](../../generate-view-from-table/SKILL.md) skill (`lkr tools lookml create --folder bronze`, `generate_lookml_with_new_files`, or MCP `generate_view_from_table` / `create_view_from_table`) to autogenerate or refresh `bronze/:table.view.lkml`, pull it locally, and re-run `lkr parse sql-lookml-compare`. Never hand-edit `bronze/:table.view.lkml`.
42
+
43
+ ### Bronze refinement (`bronze/:table.refinement.lkml`, on-demand only)
44
+
45
+ Do not create `bronze/:table.refinement.lkml` automatically. Only create it when adding transformations, single-table measures (`sum`, `average`, `count_distinct`, single-table filtered measures, and single-table ratios), single or composite primary keys, `hidden: yes` flags, partition metadata comments, or descriptions and labels that the generator does not produce. Every dimension and measure in a Bronze refinement must only reference fields within that single table across its entire dependency DAG.
46
+
47
+ ### Silver layer (cross-view `_fields` views and fact Explores)
48
+
49
+ Any dimension or measure that anywhere in its dependency DAG references different views belongs in its own dedicated Silver view named by sorting the referenced view names alphabetically and appending `_fields` (`silver/:view_a_:view_b_fields.view.lkml`), and is bare-joined (`sql: ;;`, `relationship: one_to_one`) into the Explore that joins those views together. For example, if `order_items.gross_margin` is `${order_items.sale_price} - ${inventory_items.cost}` and `order_items.total_gross_margin` is `SUM(${order_items.gross_margin})`, move both `gross_margin` and `total_gross_margin` (`type: sum`, `sql: ${gross_margin} ;;`) into `silver/inventory_items_order_items_fields.view.lkml` and bare-join `inventory_items_order_items_fields` into the Explore that joins `order_items` and `inventory_items`.
50
+
51
+ Before creating a new Explore in `silver/explores/`, inspect `queries[].explores` and `queries[].explore_queries`. If the queried table (`products`, `users`) is a dimensional or entity lookup table joined into an existing fact Silver Explore (`order_items`, `orders`, `events`), do not create a standalone `products.explore.lkml` or `users.explore.lkml`. Serve the query from the existing fact Silver Explore instead.
52
+
53
+ Only create a new `silver/explores/:table.explore.lkml` when the table in the `FROM` clause is a fact, event, or transaction table and no Silver Explore is anchored on it yet. Map `WHERE` clauses (`created_at >= '2026-01-01'`) to runtime Explore filters (`query: { filters: [...] }` Quick Start blocks or Looker `WriteQuery.filters`), and never hardcode `sql_always_where` for ad-hoc query dates. Note that Looker's `explore: { query: {} }` Quick Start syntax only supports modeled `dimensions`, `measures`, `filters`, `pivots`, `subtotals`, `limit`, `sorts`, `timezone`, and `description`; Table Calculations and Custom Fields cannot be placed inside an `explore: { query: {} }` block.
54
+
55
+ ## LookML template (single-table measures in Bronze refinement)
56
+
57
+ ```lookml
58
+ # File: bronze/orders.refinement.lkml
59
+ # (Created because total_revenue is not generated automatically; its entire DAG references only orders)
60
+ include: "orders.view.lkml"
61
+
62
+ view: +orders {
63
+ measure: total_revenue {
64
+ type: sum
65
+ sql: ${sale_price} ;;
66
+ value_format_name: usd
67
+ description: "Sum of order sale price."
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## Architecture rules
73
+
74
+ The `bronze/:table.view.lkml` file is strictly autogenerated (`1:1` physical columns plus `measure: count`) and never hand-edited. The `bronze/:table.refinement.lkml` file is created on-demand only when adding primary keys, single-table measures, custom dimensions or durations, partition metadata comments, or governance attributes whose entire dependency DAG stays within that single table.
75
+
76
+ Silver Explores (`silver/explores/:fact.explore.lkml`) are created only for fact or event tables in `FROM`. Lookup and entity tables (`users`, `products`, `calendar_dates`) are joined into fact Explores and never receive standalone Silver Explores. Any dimension or measure whose dependency DAG spans multiple views lives in `silver/:view_a_:view_b_fields.view.lkml` and is bare-joined (`sql: ;;`) into the Explore.