recon-gen 11.0.0__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 (324) hide show
  1. recon_gen-11.0.0/LICENSE +21 -0
  2. recon_gen-11.0.0/PKG-INFO +481 -0
  3. recon_gen-11.0.0/README.md +397 -0
  4. recon_gen-11.0.0/pyproject.toml +362 -0
  5. recon_gen-11.0.0/setup.cfg +4 -0
  6. recon_gen-11.0.0/src/recon_gen/__init__.py +3 -0
  7. recon_gen-11.0.0/src/recon_gen/__main__.py +6 -0
  8. recon_gen-11.0.0/src/recon_gen/_l2_fixtures/sasquatch_pr.yaml +940 -0
  9. recon_gen-11.0.0/src/recon_gen/_l2_fixtures/spec_example.yaml +202 -0
  10. recon_gen-11.0.0/src/recon_gen/apps/__init__.py +1 -0
  11. recon_gen-11.0.0/src/recon_gen/apps/executives/__init__.py +1 -0
  12. recon_gen-11.0.0/src/recon_gen/apps/executives/app.py +847 -0
  13. recon_gen-11.0.0/src/recon_gen/apps/executives/datasets.py +294 -0
  14. recon_gen-11.0.0/src/recon_gen/apps/investigation/__init__.py +1 -0
  15. recon_gen-11.0.0/src/recon_gen/apps/investigation/app.py +1170 -0
  16. recon_gen-11.0.0/src/recon_gen/apps/investigation/constants.py +149 -0
  17. recon_gen-11.0.0/src/recon_gen/apps/investigation/datasets.py +764 -0
  18. recon_gen-11.0.0/src/recon_gen/apps/l1_dashboard/__init__.py +0 -0
  19. recon_gen-11.0.0/src/recon_gen/apps/l1_dashboard/_default_l2.yaml +202 -0
  20. recon_gen-11.0.0/src/recon_gen/apps/l1_dashboard/_l2.py +42 -0
  21. recon_gen-11.0.0/src/recon_gen/apps/l1_dashboard/app.py +2633 -0
  22. recon_gen-11.0.0/src/recon_gen/apps/l1_dashboard/datasets.py +1444 -0
  23. recon_gen-11.0.0/src/recon_gen/apps/l2_flow_tracing/__init__.py +0 -0
  24. recon_gen-11.0.0/src/recon_gen/apps/l2_flow_tracing/app.py +1395 -0
  25. recon_gen-11.0.0/src/recon_gen/apps/l2_flow_tracing/datasets.py +2260 -0
  26. recon_gen-11.0.0/src/recon_gen/cli/__init__.py +57 -0
  27. recon_gen-11.0.0/src/recon_gen/cli/_app_builders.py +303 -0
  28. recon_gen-11.0.0/src/recon_gen/cli/_helpers.py +319 -0
  29. recon_gen-11.0.0/src/recon_gen/cli/_html_serve.py +326 -0
  30. recon_gen-11.0.0/src/recon_gen/cli/audit/__init__.py +1733 -0
  31. recon_gen-11.0.0/src/recon_gen/cli/audit/markdown.py +879 -0
  32. recon_gen-11.0.0/src/recon_gen/cli/audit/pdf.py +2439 -0
  33. recon_gen-11.0.0/src/recon_gen/cli/dashboards.py +134 -0
  34. recon_gen-11.0.0/src/recon_gen/cli/data.py +304 -0
  35. recon_gen-11.0.0/src/recon_gen/cli/docs.py +722 -0
  36. recon_gen-11.0.0/src/recon_gen/cli/json.py +247 -0
  37. recon_gen-11.0.0/src/recon_gen/cli/schema.py +126 -0
  38. recon_gen-11.0.0/src/recon_gen/cli/studio.py +148 -0
  39. recon_gen-11.0.0/src/recon_gen/common/__init__.py +1 -0
  40. recon_gen-11.0.0/src/recon_gen/common/aging.py +80 -0
  41. recon_gen-11.0.0/src/recon_gen/common/aws_rds.py +209 -0
  42. recon_gen-11.0.0/src/recon_gen/common/browser/__init__.py +37 -0
  43. recon_gen-11.0.0/src/recon_gen/common/browser/helpers.py +1939 -0
  44. recon_gen-11.0.0/src/recon_gen/common/browser/screenshot.py +296 -0
  45. recon_gen-11.0.0/src/recon_gen/common/cleanup.py +322 -0
  46. recon_gen-11.0.0/src/recon_gen/common/clickability.py +67 -0
  47. recon_gen-11.0.0/src/recon_gen/common/config.py +909 -0
  48. recon_gen-11.0.0/src/recon_gen/common/dataset_contract.py +476 -0
  49. recon_gen-11.0.0/src/recon_gen/common/datasource.py +200 -0
  50. recon_gen-11.0.0/src/recon_gen/common/db.py +896 -0
  51. recon_gen-11.0.0/src/recon_gen/common/deploy.py +367 -0
  52. recon_gen-11.0.0/src/recon_gen/common/drill.py +284 -0
  53. recon_gen-11.0.0/src/recon_gen/common/env_keys.py +684 -0
  54. recon_gen-11.0.0/src/recon_gen/common/etl_examples.py +448 -0
  55. recon_gen-11.0.0/src/recon_gen/common/handbook/__init__.py +29 -0
  56. recon_gen-11.0.0/src/recon_gen/common/handbook/diagrams.py +1075 -0
  57. recon_gen-11.0.0/src/recon_gen/common/handbook/invariants.py +332 -0
  58. recon_gen-11.0.0/src/recon_gen/common/handbook/l2ft_exceptions.py +243 -0
  59. recon_gen-11.0.0/src/recon_gen/common/handbook/vocabulary.py +525 -0
  60. recon_gen-11.0.0/src/recon_gen/common/html/__init__.py +59 -0
  61. recon_gen-11.0.0/src/recon_gen/common/html/__main__.py +57 -0
  62. recon_gen-11.0.0/src/recon_gen/common/html/_data_shape.py +282 -0
  63. recon_gen-11.0.0/src/recon_gen/common/html/_db_fetcher.py +277 -0
  64. recon_gen-11.0.0/src/recon_gen/common/html/_smoke_app.py +437 -0
  65. recon_gen-11.0.0/src/recon_gen/common/html/_sql_executor.py +450 -0
  66. recon_gen-11.0.0/src/recon_gen/common/html/_studio_assets/data.css +554 -0
  67. recon_gen-11.0.0/src/recon_gen/common/html/_studio_assets/diagram.css +376 -0
  68. recon_gen-11.0.0/src/recon_gen/common/html/_studio_assets/diagram.js +575 -0
  69. recon_gen-11.0.0/src/recon_gen/common/html/_studio_assets/editor.css +420 -0
  70. recon_gen-11.0.0/src/recon_gen/common/html/_studio_editor_routes.py +2217 -0
  71. recon_gen-11.0.0/src/recon_gen/common/html/_studio_routes.py +2202 -0
  72. recon_gen-11.0.0/src/recon_gen/common/html/_studio_training.py +169 -0
  73. recon_gen-11.0.0/src/recon_gen/common/html/_tree_fetcher.py +403 -0
  74. recon_gen-11.0.0/src/recon_gen/common/html/_tree_filter_specs.py +160 -0
  75. recon_gen-11.0.0/src/recon_gen/common/html/_visual_sql.py +178 -0
  76. recon_gen-11.0.0/src/recon_gen/common/html/assets/input.css +126 -0
  77. recon_gen-11.0.0/src/recon_gen/common/html/assets/js/bootstrap.js +1411 -0
  78. recon_gen-11.0.0/src/recon_gen/common/html/assets/js/dev_log.js +154 -0
  79. recon_gen-11.0.0/src/recon_gen/common/html/assets/output.css +2 -0
  80. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/css/flatpickr.min.css +13 -0
  81. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/css/nouislider.min.css +1 -0
  82. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/css/tom-select.min.css +2 -0
  83. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/js/ctxmenu.min.js +2 -0
  84. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/js/d3-sankey.min.js +2 -0
  85. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/js/d3.min.js +2 -0
  86. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/js/flatpickr.min.js +2 -0
  87. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/js/htmx.min.js +1 -0
  88. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/js/nouislider.min.js +1 -0
  89. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/js/tom-select.complete.min.js +440 -0
  90. recon_gen-11.0.0/src/recon_gen/common/html/assets/vendor/vendor.lock +92 -0
  91. recon_gen-11.0.0/src/recon_gen/common/html/assets/widgets-theme.css +210 -0
  92. recon_gen-11.0.0/src/recon_gen/common/html/render.py +1357 -0
  93. recon_gen-11.0.0/src/recon_gen/common/html/server.py +677 -0
  94. recon_gen-11.0.0/src/recon_gen/common/ids.py +36 -0
  95. recon_gen-11.0.0/src/recon_gen/common/l2/__init__.py +82 -0
  96. recon_gen-11.0.0/src/recon_gen/common/l2/auto_scenario.py +1342 -0
  97. recon_gen-11.0.0/src/recon_gen/common/l2/cache.py +135 -0
  98. recon_gen-11.0.0/src/recon_gen/common/l2/coverage.py +224 -0
  99. recon_gen-11.0.0/src/recon_gen/common/l2/deploy_pipeline.py +1071 -0
  100. recon_gen-11.0.0/src/recon_gen/common/l2/derived.py +96 -0
  101. recon_gen-11.0.0/src/recon_gen/common/l2/editor.py +747 -0
  102. recon_gen-11.0.0/src/recon_gen/common/l2/loader.py +1196 -0
  103. recon_gen-11.0.0/src/recon_gen/common/l2/primitives.py +426 -0
  104. recon_gen-11.0.0/src/recon_gen/common/l2/schema.py +1939 -0
  105. recon_gen-11.0.0/src/recon_gen/common/l2/seed.py +4420 -0
  106. recon_gen-11.0.0/src/recon_gen/common/l2/serializer.py +329 -0
  107. recon_gen-11.0.0/src/recon_gen/common/l2/studio_state.py +308 -0
  108. recon_gen-11.0.0/src/recon_gen/common/l2/tg_cache.py +354 -0
  109. recon_gen-11.0.0/src/recon_gen/common/l2/theme.py +62 -0
  110. recon_gen-11.0.0/src/recon_gen/common/l2/topology.py +1368 -0
  111. recon_gen-11.0.0/src/recon_gen/common/l2/trainer.py +162 -0
  112. recon_gen-11.0.0/src/recon_gen/common/l2/trainer_timeline.py +170 -0
  113. recon_gen-11.0.0/src/recon_gen/common/l2/validate.py +964 -0
  114. recon_gen-11.0.0/src/recon_gen/common/models.py +1456 -0
  115. recon_gen-11.0.0/src/recon_gen/common/pdf/__init__.py +1 -0
  116. recon_gen-11.0.0/src/recon_gen/common/pdf/audit_chrome.py +156 -0
  117. recon_gen-11.0.0/src/recon_gen/common/pdf/signing.py +91 -0
  118. recon_gen-11.0.0/src/recon_gen/common/persona.py +59 -0
  119. recon_gen-11.0.0/src/recon_gen/common/probe.py +167 -0
  120. recon_gen-11.0.0/src/recon_gen/common/provenance.py +333 -0
  121. recon_gen-11.0.0/src/recon_gen/common/rich_text.py +247 -0
  122. recon_gen-11.0.0/src/recon_gen/common/sheets/__init__.py +7 -0
  123. recon_gen-11.0.0/src/recon_gen/common/sheets/app_info.py +361 -0
  124. recon_gen-11.0.0/src/recon_gen/common/sql/__init__.py +89 -0
  125. recon_gen-11.0.0/src/recon_gen/common/sql/app2_filters.py +126 -0
  126. recon_gen-11.0.0/src/recon_gen/common/sql/dialect.py +856 -0
  127. recon_gen-11.0.0/src/recon_gen/common/theme.py +214 -0
  128. recon_gen-11.0.0/src/recon_gen/common/tree/__init__.py +219 -0
  129. recon_gen-11.0.0/src/recon_gen/common/tree/_helpers.py +145 -0
  130. recon_gen-11.0.0/src/recon_gen/common/tree/actions.py +257 -0
  131. recon_gen-11.0.0/src/recon_gen/common/tree/calc_fields.py +129 -0
  132. recon_gen-11.0.0/src/recon_gen/common/tree/controls.py +522 -0
  133. recon_gen-11.0.0/src/recon_gen/common/tree/datasets.py +211 -0
  134. recon_gen-11.0.0/src/recon_gen/common/tree/fields.py +393 -0
  135. recon_gen-11.0.0/src/recon_gen/common/tree/filters.py +737 -0
  136. recon_gen-11.0.0/src/recon_gen/common/tree/formatting.py +107 -0
  137. recon_gen-11.0.0/src/recon_gen/common/tree/parameters.py +157 -0
  138. recon_gen-11.0.0/src/recon_gen/common/tree/structure.py +1844 -0
  139. recon_gen-11.0.0/src/recon_gen/common/tree/text_boxes.py +50 -0
  140. recon_gen-11.0.0/src/recon_gen/common/tree/visuals.py +842 -0
  141. recon_gen-11.0.0/src/recon_gen/common/variant.py +449 -0
  142. recon_gen-11.0.0/src/recon_gen/docs/L1_Invariants.md +264 -0
  143. recon_gen-11.0.0/src/recon_gen/docs/L2FT_Exceptions.md +143 -0
  144. recon_gen-11.0.0/src/recon_gen/docs/SPEC.md +1022 -0
  145. recon_gen-11.0.0/src/recon_gen/docs/Schema_v6.md +482 -0
  146. recon_gen-11.0.0/src/recon_gen/docs/_diagrams/conceptual/double-entry.dot +28 -0
  147. recon_gen-11.0.0/src/recon_gen/docs/_diagrams/conceptual/escrow-with-reversal.dot +23 -0
  148. recon_gen-11.0.0/src/recon_gen/docs/_diagrams/conceptual/eventual-consistency.dot +22 -0
  149. recon_gen-11.0.0/src/recon_gen/docs/_diagrams/conceptual/open-vs-closed-loop.dot +31 -0
  150. recon_gen-11.0.0/src/recon_gen/docs/_diagrams/conceptual/sweep-net-settle.dot +24 -0
  151. recon_gen-11.0.0/src/recon_gen/docs/_diagrams/conceptual/vouchering.dot +27 -0
  152. recon_gen-11.0.0/src/recon_gen/docs/_macros/README.md +16 -0
  153. recon_gen-11.0.0/src/recon_gen/docs/api/common-foundations.md +54 -0
  154. recon_gen-11.0.0/src/recon_gen/docs/api/index.md +41 -0
  155. recon_gen-11.0.0/src/recon_gen/docs/api/tree-actions.md +9 -0
  156. recon_gen-11.0.0/src/recon_gen/docs/api/tree-data.md +15 -0
  157. recon_gen-11.0.0/src/recon_gen/docs/api/tree-filters-controls.md +17 -0
  158. recon_gen-11.0.0/src/recon_gen/docs/api/tree-structure.md +8 -0
  159. recon_gen-11.0.0/src/recon_gen/docs/api/tree-visuals.md +10 -0
  160. recon_gen-11.0.0/src/recon_gen/docs/concepts/accounting/double-entry.md +52 -0
  161. recon_gen-11.0.0/src/recon_gen/docs/concepts/accounting/escrow-with-reversal.md +52 -0
  162. recon_gen-11.0.0/src/recon_gen/docs/concepts/accounting/eventual-consistency.md +56 -0
  163. recon_gen-11.0.0/src/recon_gen/docs/concepts/accounting/index.md +27 -0
  164. recon_gen-11.0.0/src/recon_gen/docs/concepts/accounting/open-vs-closed-loop.md +56 -0
  165. recon_gen-11.0.0/src/recon_gen/docs/concepts/accounting/sweep-net-settle.md +55 -0
  166. recon_gen-11.0.0/src/recon_gen/docs/concepts/accounting/vouchering.md +62 -0
  167. recon_gen-11.0.0/src/recon_gen/docs/concepts/index.md +22 -0
  168. recon_gen-11.0.0/src/recon_gen/docs/concepts/l2/account-template.md +34 -0
  169. recon_gen-11.0.0/src/recon_gen/docs/concepts/l2/account.md +34 -0
  170. recon_gen-11.0.0/src/recon_gen/docs/concepts/l2/chain.md +40 -0
  171. recon_gen-11.0.0/src/recon_gen/docs/concepts/l2/index.md +34 -0
  172. recon_gen-11.0.0/src/recon_gen/docs/concepts/l2/limit-schedule.md +38 -0
  173. recon_gen-11.0.0/src/recon_gen/docs/concepts/l2/rail.md +41 -0
  174. recon_gen-11.0.0/src/recon_gen/docs/concepts/l2/transfer-template.md +40 -0
  175. recon_gen-11.0.0/src/recon_gen/docs/for-your-role/compliance-analyst.md +201 -0
  176. recon_gen-11.0.0/src/recon_gen/docs/for-your-role/etl-engineer.md +157 -0
  177. recon_gen-11.0.0/src/recon_gen/docs/for-your-role/executive.md +121 -0
  178. recon_gen-11.0.0/src/recon_gen/docs/for-your-role/index.md +11 -0
  179. recon_gen-11.0.0/src/recon_gen/docs/for-your-role/integrator.md +149 -0
  180. recon_gen-11.0.0/src/recon_gen/docs/for-your-role/operator.md +147 -0
  181. recon_gen-11.0.0/src/recon_gen/docs/handbook/audit.md +348 -0
  182. recon_gen-11.0.0/src/recon_gen/docs/handbook/customization.md +251 -0
  183. recon_gen-11.0.0/src/recon_gen/docs/handbook/etl.md +246 -0
  184. recon_gen-11.0.0/src/recon_gen/docs/handbook/executives.md +72 -0
  185. recon_gen-11.0.0/src/recon_gen/docs/handbook/investigation.md +156 -0
  186. recon_gen-11.0.0/src/recon_gen/docs/handbook/l1.md +192 -0
  187. recon_gen-11.0.0/src/recon_gen/docs/handbook/l2_flow_tracing.md +166 -0
  188. recon_gen-11.0.0/src/recon_gen/docs/handbook/seed-generator.md +434 -0
  189. recon_gen-11.0.0/src/recon_gen/docs/img/favicon.svg +8 -0
  190. recon_gen-11.0.0/src/recon_gen/docs/img/snb-mark.svg +8 -0
  191. recon_gen-11.0.0/src/recon_gen/docs/img/snb-wordmark.svg +18 -0
  192. recon_gen-11.0.0/src/recon_gen/docs/index.md +59 -0
  193. recon_gen-11.0.0/src/recon_gen/docs/reference/cli.md +19 -0
  194. recon_gen-11.0.0/src/recon_gen/docs/reference/disable-tagging.md +125 -0
  195. recon_gen-11.0.0/src/recon_gen/docs/reference/fixtures/spec_example.yaml +159 -0
  196. recon_gen-11.0.0/src/recon_gen/docs/reference/index.md +48 -0
  197. recon_gen-11.0.0/src/recon_gen/docs/reference/install.md +127 -0
  198. recon_gen-11.0.0/src/recon_gen/docs/reference/quicksight-quirks.md +752 -0
  199. recon_gen-11.0.0/src/recon_gen/docs/reference/self-host.md +70 -0
  200. recon_gen-11.0.0/src/recon_gen/docs/scenario/accounts.md +38 -0
  201. recon_gen-11.0.0/src/recon_gen/docs/scenario/chains.md +26 -0
  202. recon_gen-11.0.0/src/recon_gen/docs/scenario/index.md +107 -0
  203. recon_gen-11.0.0/src/recon_gen/docs/scenario/limit-schedules.md +19 -0
  204. recon_gen-11.0.0/src/recon_gen/docs/scenario/rails.md +34 -0
  205. recon_gen-11.0.0/src/recon_gen/docs/scenario/transfer-templates.md +29 -0
  206. recon_gen-11.0.0/src/recon_gen/docs/stylesheets/qs-graphviz-wasm.js +115 -0
  207. recon_gen-11.0.0/src/recon_gen/docs/stylesheets/qs-lightbox.js +270 -0
  208. recon_gen-11.0.0/src/recon_gen/docs/stylesheets/site.css +296 -0
  209. recon_gen-11.0.0/src/recon_gen/docs/stylesheets/wasm-graphviz/index.js +4 -0
  210. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-add-a-metadata-key.md +254 -0
  211. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-author-a-new-app-on-the-tree.md +254 -0
  212. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-brand-my-handbook-prose.md +298 -0
  213. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-configure-the-deploy.md +291 -0
  214. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-extend-canonical-values.md +236 -0
  215. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-map-my-database.md +218 -0
  216. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-publish-docs-against-my-l2.md +103 -0
  217. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-reskin-the-dashboards.md +264 -0
  218. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-run-my-first-deploy.md +281 -0
  219. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-swap-dataset-sql.md +248 -0
  220. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/customization/how-do-i-test-my-customization.md +313 -0
  221. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/etl/how-do-i-add-a-metadata-key.md +192 -0
  222. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/etl/how-do-i-populate-transactions.md +196 -0
  223. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/etl/how-do-i-prove-my-etl-is-working.md +242 -0
  224. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/etl/how-do-i-tag-a-force-posted-transfer.md +173 -0
  225. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/etl/how-do-i-validate-a-single-account-day.md +252 -0
  226. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/etl/what-do-i-do-when-demo-passes-but-prod-fails.md +283 -0
  227. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/index.md +57 -0
  228. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/investigation/what-does-this-accounts-money-network-look-like.md +205 -0
  229. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/investigation/where-did-this-transfer-originate.md +188 -0
  230. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/investigation/which-pair-just-spiked.md +171 -0
  231. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/investigation/who-is-getting-money-from-too-many-senders.md +174 -0
  232. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/daily-statement.md +60 -0
  233. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/drift-timelines.md +52 -0
  234. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/drift.md +53 -0
  235. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/getting-started.md +36 -0
  236. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/limit-breach.md +48 -0
  237. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/overdraft.md +44 -0
  238. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/pending-aging.md +48 -0
  239. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/supersession-audit.md +58 -0
  240. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/todays-exceptions.md +61 -0
  241. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/transactions.md +67 -0
  242. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/l1/unbundled-aging.md +50 -0
  243. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/daily-statement-01-clean.png +0 -0
  244. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/daily-statement-02-drift.png +0 -0
  245. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/daily-statement-03-overdraft.png +0 -0
  246. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-breakdown.png +0 -0
  247. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-ach-origination-non-zero.png +0 -0
  248. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-ach-sweep-no-fed-confirmation.png +0 -0
  249. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-concentration-master-sweep-drift.png +0 -0
  250. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-fed-card-no-internal-catchup.png +0 -0
  251. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-gl-vs-fed-master-drift.png +0 -0
  252. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-internal-reversal-uncredited.png +0 -0
  253. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-internal-transfer-suspense-non-zero.png +0 -0
  254. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-ledger-drift.png +0 -0
  255. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-non-zero-transfers.png +0 -0
  256. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-stuck-in-internal-transfer-suspense.png +0 -0
  257. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-sub-ledger-drift.png +0 -0
  258. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-sub-ledger-limit-breach.png +0 -0
  259. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-sub-ledger-overdraft.png +0 -0
  260. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-filtered-sweep-target-non-zero.png +0 -0
  261. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-overview.png +0 -0
  262. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/todays-exceptions-table.png +0 -0
  263. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/trends-aging-by-check.png +0 -0
  264. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/trends-drift-timelines.png +0 -0
  265. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/trends-expected-zero-rollup.png +0 -0
  266. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/trends-per-check-by-day.png +0 -0
  267. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/ar/trends-two-sided-rollup.png +0 -0
  268. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/exec/exec-sheet-account-coverage.png +0 -0
  269. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/exec/exec-sheet-app-info.png +0 -0
  270. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/exec/exec-sheet-getting-started.png +0 -0
  271. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/exec/exec-sheet-money-moved.png +0 -0
  272. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/exec/exec-sheet-transaction-volume.png +0 -0
  273. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/inv/inv-sheet-account-network.png +0 -0
  274. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/inv/inv-sheet-anomalies.png +0 -0
  275. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/inv/inv-sheet-app-info.png +0 -0
  276. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/inv/inv-sheet-fanout.png +0 -0
  277. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/inv/inv-sheet-getting-started.png +0 -0
  278. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/inv/inv-sheet-money-trail.png +0 -0
  279. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-app-info.png +0 -0
  280. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-daily-statement.png +0 -0
  281. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-drift-timelines.png +0 -0
  282. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-drift.png +0 -0
  283. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-getting-started.png +0 -0
  284. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-limit-breach.png +0 -0
  285. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-overdraft.png +0 -0
  286. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-pending-aging.png +0 -0
  287. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-supersession-audit.png +0 -0
  288. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-todays-exceptions.png +0 -0
  289. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-transactions.png +0 -0
  290. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l1/l1-sheet-unbundled-aging.png +0 -0
  291. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l2ft/l2ft-sheet-app-info.png +0 -0
  292. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l2ft/l2ft-sheet-chains.png +0 -0
  293. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l2ft/l2ft-sheet-getting-started.png +0 -0
  294. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l2ft/l2ft-sheet-l2-exceptions.png +0 -0
  295. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l2ft/l2ft-sheet-rails.png +0 -0
  296. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/l2ft/l2ft-sheet-transfer-templates.png +0 -0
  297. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/did-all-merchants-get-paid-01-kpis.png +0 -0
  298. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/did-all-merchants-get-paid-02-settlements.png +0 -0
  299. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/how-much-did-we-return-01-kpi.png +0 -0
  300. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/how-much-did-we-return-02-table.png +0 -0
  301. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/how-much-did-we-return-03-aging.png +0 -0
  302. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/wheres-my-money-for-merchant-01-sales.png +0 -0
  303. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/wheres-my-money-for-merchant-02-settlements.png +0 -0
  304. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/wheres-my-money-for-merchant-03-payments.png +0 -0
  305. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/which-sales-never-made-it-to-settlement-01-kpi.png +0 -0
  306. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/which-sales-never-made-it-to-settlement-02-table.png +0 -0
  307. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/which-sales-never-made-it-to-settlement-03-aging.png +0 -0
  308. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/why-does-this-settlement-look-short-01-kpi.png +0 -0
  309. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/why-does-this-settlement-look-short-02-table.png +0 -0
  310. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/why-does-this-settlement-look-short-03-aging.png +0 -0
  311. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/why-doesnt-this-payment-match-the-settlement-01-kpi.png +0 -0
  312. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/why-doesnt-this-payment-match-the-settlement-02-table.png +0 -0
  313. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/why-doesnt-this-payment-match-the-settlement-03-aging.png +0 -0
  314. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/why-is-this-external-transaction-unmatched-01-kpis.png +0 -0
  315. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/why-is-this-external-transaction-unmatched-02-orphan.png +0 -0
  316. recon_gen-11.0.0/src/recon_gen/docs/walkthroughs/screenshots/pr/why-is-this-external-transaction-unmatched-03-drift.png +0 -0
  317. recon_gen-11.0.0/src/recon_gen/main.py +606 -0
  318. recon_gen-11.0.0/src/recon_gen/mkdocs.yml +216 -0
  319. recon_gen-11.0.0/src/recon_gen.egg-info/PKG-INFO +481 -0
  320. recon_gen-11.0.0/src/recon_gen.egg-info/SOURCES.txt +322 -0
  321. recon_gen-11.0.0/src/recon_gen.egg-info/dependency_links.txt +1 -0
  322. recon_gen-11.0.0/src/recon_gen.egg-info/entry_points.txt +2 -0
  323. recon_gen-11.0.0/src/recon_gen.egg-info/requires.txt +68 -0
  324. recon_gen-11.0.0/src/recon_gen.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christopher Hotchkiss
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,481 @@
1
+ Metadata-Version: 2.4
2
+ Name: recon-gen
3
+ Version: 11.0.0
4
+ Summary: Programmatic AWS QuickSight analysis generator for financial reporting
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://chotchki.github.io/recon-gen/
7
+ Project-URL: Documentation, https://chotchki.github.io/recon-gen/
8
+ Project-URL: Source, https://github.com/chotchki/recon-gen
9
+ Project-URL: Issues, https://github.com/chotchki/recon-gen/issues
10
+ Project-URL: Changelog, https://github.com/chotchki/recon-gen/blob/main/RELEASE_NOTES.md
11
+ Keywords: quicksight,aws,dashboards,reconciliation,analytics,finance
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
19
+ Classifier: Topic :: Software Development :: Code Generators
20
+ Requires-Python: >=3.13
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: click>=8.1
24
+ Requires-Dist: pyyaml>=6.0
25
+ Requires-Dist: graphviz>=0.20
26
+ Provides-Extra: deploy
27
+ Requires-Dist: boto3>=1.34; extra == "deploy"
28
+ Requires-Dist: botocore[crt]>=1.34; extra == "deploy"
29
+ Provides-Extra: demo
30
+ Requires-Dist: psycopg[binary,pool]>=3.1; extra == "demo"
31
+ Provides-Extra: demo-oracle
32
+ Requires-Dist: oracledb>=2.0; extra == "demo-oracle"
33
+ Provides-Extra: audit
34
+ Requires-Dist: reportlab>=4.0; extra == "audit"
35
+ Requires-Dist: pypdf>=4.0; extra == "audit"
36
+ Requires-Dist: pyhanko>=0.30; extra == "audit"
37
+ Provides-Extra: serve
38
+ Requires-Dist: starlette>=0.40; extra == "serve"
39
+ Requires-Dist: uvicorn[standard]>=0.30; extra == "serve"
40
+ Requires-Dist: python-multipart>=0.0.18; extra == "serve"
41
+ Requires-Dist: httpx>=0.27; extra == "serve"
42
+ Requires-Dist: aiosqlite>=0.19; extra == "serve"
43
+ Provides-Extra: dev
44
+ Requires-Dist: pytest>=7.0; extra == "dev"
45
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
46
+ Requires-Dist: pytest-xdist>=3.5; extra == "dev"
47
+ Requires-Dist: pytest-rerunfailures>=14.0; extra == "dev"
48
+ Requires-Dist: boto3>=1.34; extra == "dev"
49
+ Requires-Dist: botocore[crt]>=1.34; extra == "dev"
50
+ Requires-Dist: boto3-stubs[quicksight,rds]>=1.34; extra == "dev"
51
+ Requires-Dist: testcontainers[oracle,postgres]>=3.7; extra == "dev"
52
+ Requires-Dist: psycopg[binary,pool]>=3.1; extra == "dev"
53
+ Requires-Dist: aiosqlite>=0.19; extra == "dev"
54
+ Requires-Dist: oracledb>=2.0; extra == "dev"
55
+ Requires-Dist: reportlab>=4.0; extra == "dev"
56
+ Requires-Dist: pypdf>=4.0; extra == "dev"
57
+ Requires-Dist: build>=1.0; extra == "dev"
58
+ Requires-Dist: twine>=5.0; extra == "dev"
59
+ Requires-Dist: pyright>=1.1.380; extra == "dev"
60
+ Requires-Dist: playwright>=1.40; extra == "dev"
61
+ Requires-Dist: starlette>=0.40; extra == "dev"
62
+ Requires-Dist: uvicorn[standard]>=0.30; extra == "dev"
63
+ Requires-Dist: python-multipart>=0.0.18; extra == "dev"
64
+ Requires-Dist: httpx>=0.27; extra == "dev"
65
+ Provides-Extra: docs
66
+ Requires-Dist: mkdocs>=1.6; extra == "docs"
67
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
68
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == "docs"
69
+ Requires-Dist: mkdocs-click>=0.8; extra == "docs"
70
+ Requires-Dist: mkdocs-macros-plugin>=1.3; extra == "docs"
71
+ Requires-Dist: graphviz>=0.20; extra == "docs"
72
+ Provides-Extra: e2e
73
+ Requires-Dist: pytest>=7.0; extra == "e2e"
74
+ Requires-Dist: pytest-xdist>=3.5; extra == "e2e"
75
+ Requires-Dist: boto3>=1.34; extra == "e2e"
76
+ Requires-Dist: botocore[crt]>=1.34; extra == "e2e"
77
+ Requires-Dist: playwright>=1.40; extra == "e2e"
78
+ Requires-Dist: starlette>=0.40; extra == "e2e"
79
+ Requires-Dist: uvicorn[standard]>=0.30; extra == "e2e"
80
+ Requires-Dist: python-multipart>=0.0.18; extra == "e2e"
81
+ Requires-Dist: httpx>=0.27; extra == "e2e"
82
+ Requires-Dist: aiosqlite>=0.19; extra == "e2e"
83
+ Dynamic: license-file
84
+
85
+ # Recon Generator
86
+
87
+ [![CI](https://github.com/chotchki/recon-gen/actions/workflows/ci.yml/badge.svg)](https://github.com/chotchki/recon-gen/actions/workflows/ci.yml)
88
+ [![Coverage](https://raw.githubusercontent.com/chotchki/recon-gen/badges/coverage-badge.svg)](https://github.com/chotchki/recon-gen/blob/badges/coverage-report.md)
89
+ [![PyPI](https://img.shields.io/pypi/v/recon-gen.svg)](https://pypi.org/project/recon-gen/)
90
+
91
+ Python tool that programmatically generates AWS QuickSight JSON definitions (theme, datasets, analyses, dashboards) and deploys them via boto3. Ships **four bundled QuickSight apps** plus a **regulator-ready PDF audit report**, all L2-fed off one institution YAML:
92
+
93
+ - **L1 Dashboard** — persona-blind L1 invariant violation surface (drift / overdraft / limit breach / stuck pending / stuck unbundled / supersession audit / today's exceptions / daily statement / transactions). 11 sheets. Configured by an L2 instance YAML; the same dashboard renders against any institution that declares its accounts / rails / templates / chains / limit schedules in L2 form.
94
+ - **L2 Flow Tracing** — Rails / Chains / Transfer Templates / L2 Hygiene Exceptions for the integrator validating their L2 instance against the SPEC.
95
+ - **Investigation** — compliance / AML triage: 4 question-shaped sheets — recipient fanout, volume anomalies, money-trail provenance, account-network graphs — over the shared base ledger.
96
+ - **Executives** — board-cadence rollups: account coverage (open vs active), transaction volume over time, money moved (gross + net) over time. Reads only the shared base tables — no Executives-specific schema.
97
+
98
+ CLI is organized as five artifact groups: `recon-gen schema|data|json|docs|audit`. Each artifact has `apply`/`clean`/`test` (plus a few extras); destructive operations default to emit and require `--execute` to actually run. The `audit` group also exposes a `verify` subcommand for recomputing a generated PDF's provenance fingerprint. Change the Python (or ask Claude), re-run `json apply --execute`, get a new dashboard.
99
+
100
+ ## Demo Docs
101
+
102
+ The demo ships with task-shaped handbooks deployed to GitHub Pages at **[chotchki.github.io/recon-gen](https://chotchki.github.io/recon-gen/)**.
103
+
104
+ - **[L1 Dashboard handbook](https://chotchki.github.io/recon-gen/handbook/l1/)** — 11 sheets covering 5 baseline L1 invariants + 2 aging-watch invariants + supersession audit + per-account-day walk + raw posting ledger. Switch the L2 instance to switch the persona prose without touching dashboard code.
105
+ - **[L2 Flow Tracing handbook](https://chotchki.github.io/recon-gen/handbook/l2_flow_tracing/)** — Rails / Chains / Transfer Templates / L2 Hygiene Exceptions for L2 spec verification.
106
+ - **[Investigation handbook](https://chotchki.github.io/recon-gen/handbook/investigation/)** — Compliance / Investigation team flow. 4 walkthroughs, one per sheet's question.
107
+ - **[Executives handbook](https://chotchki.github.io/recon-gen/handbook/executives/)** — board scorecard: account coverage, transaction volume, money moved.
108
+ - **[Data Integration handbook](https://chotchki.github.io/recon-gen/handbook/etl/)** — how the Data Integration Team maps an upstream system into `<prefix>_transactions` + `<prefix>_daily_balances`, validates the load, and extends the metadata contract.
109
+ - **[Audit Reconciliation Report handbook](https://chotchki.github.io/recon-gen/handbook/audit/)** — regulator-ready PDF generated by `recon-gen audit apply`; covers the L1 invariants, embeds a provenance fingerprint, optionally auto-signs via pyHanko.
110
+
111
+ Source lives in `src/recon_gen/docs/` (shipped with the wheel — extract with `recon-gen docs export -o ./somewhere/`); rebuild locally with `recon-gen docs serve`.
112
+
113
+ ## Why this exists
114
+
115
+ The customer for these reports doesn't know exactly what they want yet. Rather than click through the QuickSight console and lose the work when requirements change, everything is generated from code and deployed idempotently (delete-then-create). Iteration is one command.
116
+
117
+ ## The four apps
118
+
119
+ ### L1 Dashboard — 11 tabs
120
+
121
+ The recommended path for new integrators. Configured by an L2 instance YAML — declare your institution once (accounts, rails, transfer templates, chains, limit schedules, per-rail aging caps), and the same dashboard renders against you. Switching the L2 instance switches the prose on every TextBox without touching dashboard code.
122
+
123
+ | Tab | What it shows |
124
+ |---|---|
125
+ | Getting Started | Welcome + L2 coverage inventory pulled from the L2 instance's prose. |
126
+ | Drift | Leaf + parent account balance drift detail tables. Right-click any row → Daily Statement for that account-day. |
127
+ | Drift Timelines | KPI for largest single-day drift + 2 LineCharts (one line per `account_role`) tracking Σ ABS(drift) over the visible date range. |
128
+ | Overdraft | KPI + violations table for internal accounts holding negative money at EOD. Right-click → Daily Statement. |
129
+ | Limit Breach | KPI + per-(account, day, transfer_type) breach table. Caps inlined from L2 LimitSchedules at schema-emit time. |
130
+ | Pending Aging | Stuck-Pending transactions past their rail's `max_pending_age`. KPI + 5-bucket horizontal aging bar + detail. Right-click → Transactions. |
131
+ | Unbundled Aging | Posted legs with `bundle_id IS NULL` past their rail's `max_unbundled_age`. Same KPI + bar + detail shape with 4 day-scale buckets. |
132
+ | Supersession Audit | Logical keys with multiple `entry` versions — the rewrite trail (Inflight / BundleAssignment / TechnicalCorrection). |
133
+ | Today's Exceptions | UNION across all 5 baseline invariant views scoped to the most recent business day. KPI + by-check bar + detail sorted by magnitude. |
134
+ | Daily Statement | Per-account-day walk: 5 KPIs (Opening / Debits / Credits / Closing / Drift) + every Money record posted that day. |
135
+ | Transactions | Raw posting ledger (`<prefix>_current_transactions` matview — supersession-aware). 5 dropdown filters for analyst-driven slicing. |
136
+
137
+ Reads from per-instance `<prefix>_*` views/matviews emitted by `common.l2.emit_schema(instance)`. See [L1 Invariants](https://chotchki.github.io/recon-gen/L1_Invariants/) for the per-view contract + SHOULD-constraint motivation.
138
+
139
+ ### L2 Flow Tracing — 4 tabs
140
+
141
+ | Tab | What it shows |
142
+ |---|---|
143
+ | Getting Started | Welcome + roadmap of the three flow tabs below. |
144
+ | Rails | Postings explorer + per-rail firing counts + L2 declaration cascade. |
145
+ | Chains | Parent → child rail/template firings with per-chain SUM amounts. |
146
+ | Transfer Templates | Multi-leg transfer template firings + L2 hygiene exception list. |
147
+
148
+ ### Investigation — 5 tabs
149
+
150
+ | Tab | What it shows |
151
+ |---|---|
152
+ | Getting Started | Landing page — heading + roadmap of the four question-shaped sheets below. |
153
+ | Recipient Fanout | Who is receiving money from too many distinct senders? 3 KPIs (qualifying recipients / distinct senders / total inbound) + ranked table; threshold slider sets where "too many" starts. |
154
+ | Volume Anomalies | Which sender → recipient pair just spiked above its rolling baseline? Backed by `inv_pair_rolling_anomalies` matview (rolling 2-day SUM per pair + population z-score). KPI flagged-pair count + σ distribution chart + ranked table; σ slider gates KPI + table while the chart shows the full population. |
155
+ | Money Trail | Where did this transfer originate, and where does it go? Backed by `inv_money_trail_edges` matview (recursive `WITH RECURSIVE` walk over `parent_transfer_id`). Sankey as the headline + hop-by-hop table beside it; chain-root dropdown + max-hops + min-hop-amount controls. |
156
+ | Account Network | What does this account's money network look like, on either side? Two side-by-side directional Sankeys (inbound on the left, outbound on the right, anchor visually meeting in the middle) + touching-edges table. Walk-the-flow drill: right-click any table row or left-click any Sankey node to walk the anchor to the counterparty and re-render around the new center. |
157
+
158
+ ### Executives — 4 tabs
159
+
160
+ | Tab | What it shows |
161
+ |---|---|
162
+ | Getting Started | Landing page — heading + per-sheet highlights. |
163
+ | Account Coverage | Open vs Active account KPIs + bar chart by `account_type` + detail table. The Active KPI + Active bar carry a visual-pinned `activity_count >= 1` filter so they read as "accounts that moved money in the period" while the Open KPI/bar count every row — same dataset, different scope. |
164
+ | Transaction Volume Over Time | Total transfers + average daily KPIs + daily stacked bar by `transfer_type` + per-type bar. Per-transfer pre-aggregation collapses multi-leg transfers so a 2-leg $100 movement counts as one $100 transfer, not two $200. |
165
+ | Money Moved | Gross + net amount KPIs + daily stacked bar by `transfer_type` + per-type bar. Net = inflows − outflows from the bank's perspective. |
166
+
167
+ ### Shared conventions
168
+
169
+ - **Clickable cells look clickable.** Accent-colored text = left-click drill; accent text on a pale tint background = right-click menu drill.
170
+ - Every sheet has a plain-language description; every visual has a subtitle. Coverage is asserted in unit + API e2e tests.
171
+ - All resources tagged `ManagedBy: recon-gen`; extra tags via `extra_tags` in config.
172
+
173
+ ## Quick start
174
+
175
+ ### Prerequisites
176
+
177
+ - Python 3.13+
178
+ - An AWS account with QuickSight Enterprise enabled
179
+ - Either a pre-existing QuickSight datasource ARN **or** a PostgreSQL **17+** / Oracle **19c+** database URL for demo mode (the schema uses SQL/JSON path syntax — `JSON_VALUE` / `JSON_QUERY` / `JSON_EXISTS` — supported on both engines)
180
+
181
+ ### Install from PyPI
182
+
183
+ For consumers — using a pre-existing QuickSight datasource ARN:
184
+
185
+ ```bash
186
+ pip install recon-gen
187
+ ```
188
+
189
+ For demo mode against PostgreSQL 17+ (requires `psycopg2-binary`):
190
+
191
+ ```bash
192
+ pip install "recon-gen[demo]"
193
+ ```
194
+
195
+ For demo mode against Oracle 19c+ (requires `oracledb` thin mode — no Oracle Instant Client install):
196
+
197
+ ```bash
198
+ pip install "recon-gen[demo,demo-oracle]"
199
+ ```
200
+
201
+ For demo mode against SQLite 3.38+ (no extra install — uses stdlib `sqlite3` + the JSON1 functions that ship built-in since 3.38):
202
+
203
+ ```bash
204
+ pip install recon-gen
205
+ ```
206
+
207
+ > The package was renamed from `quicksight-gen` to `recon-gen` in v11.0.0. `pip install quicksight-gen` continues to work transparently for a 1-2 month grace period after the rename via a meta-package shim; switch to `recon-gen` at your convenience.
208
+
209
+ ### Setup from source
210
+
211
+ The repo uses [uv](https://docs.astral.sh/uv/) for env / lock management
212
+ (deterministic resolution from `uv.lock`). One command sets up `.venv/`
213
+ with every extra:
214
+
215
+ ```bash
216
+ uv sync --all-extras
217
+ ```
218
+
219
+ Then invoke tools directly via the venv (no `source activate` needed):
220
+
221
+ ```bash
222
+ .venv/bin/pytest
223
+ .venv/bin/recon-gen --help
224
+ ```
225
+
226
+ For a leaner install, swap `--all-extras` for the specific extras you
227
+ need: `--extra dev` (tests + pyright + boto3), `--extra audit` (PDF
228
+ report deps), `--extra docs` (mkdocs + macros), `--extra demo` /
229
+ `--extra demo-oracle` (DB drivers).
230
+
231
+ If you'd rather stick with pip, the standard PEP-621 path still works:
232
+
233
+ ```bash
234
+ python3 -m venv .venv
235
+ .venv/bin/pip install -e ".[dev]"
236
+ ```
237
+
238
+ ### Configure
239
+
240
+ ```bash
241
+ cp config.example.yaml config.yaml
242
+ ```
243
+
244
+ Edit `config.yaml`:
245
+
246
+ ```yaml
247
+ aws_account_id: "123456789012"
248
+ aws_region: "us-east-2"
249
+
250
+ # Pre-existing QuickSight datasource ARN.
251
+ # Not required when demo_database_url is set (auto-derived).
252
+ datasource_arn: "arn:aws:quicksight:us-east-2:123456789012:datasource/your-datasource-id"
253
+
254
+ # Required: deployment identity (Z.C). `deployment_name` prefixes
255
+ # every QS resource ID; `db_table_prefix` prefixes every DB
256
+ # table/matview/dataset name. Both are required — no defaults.
257
+ deployment_name: "recon-prod"
258
+ db_table_prefix: "recon_prod"
259
+
260
+ # Optional: IAM principals granted permissions on generated resources.
261
+ principal_arns:
262
+ - "arn:aws:quicksight:us-east-1:123456789012:user/default/admin"
263
+
264
+ # Optional: additional tags on every generated resource
265
+ extra_tags:
266
+ Environment: production
267
+ Team: finance
268
+
269
+ # Optional: which database family for `data apply --execute` (default: postgres)
270
+ # dialect: "postgres" # or "oracle" or "sqlite"
271
+
272
+ # Optional: database URL for `data apply --execute` and friends
273
+ # Postgres:
274
+ # demo_database_url: "postgresql://user:pass@localhost:5432/quicksight_demo"
275
+ # Oracle (Easy Connect form, no scheme prefix):
276
+ # demo_database_url: "system/pass@localhost:1521/FREEPDB1"
277
+ # SQLite (file or in-memory; integrator-local iteration loop, no server):
278
+ # demo_database_url: "sqlite:///./demo.sqlite"
279
+ ```
280
+
281
+ > Theme is declared inline on the L2 institution YAML's `theme:` block, not on the deploy config. When the L2 instance carries no `theme:` block, AWS QuickSight CLASSIC takes over at deploy.
282
+
283
+ All values can also be set via `QS_GEN_`-prefixed environment variables (e.g. `QS_GEN_AWS_ACCOUNT_ID`). Env vars override YAML.
284
+
285
+ ### Generate and deploy
286
+
287
+ ```bash
288
+ # Generate JSON for all four bundled apps to out/
289
+ recon-gen json apply -c config.yaml -o out/
290
+
291
+ # Same emit, then deploy to AWS (delete-then-create, idempotent)
292
+ recon-gen json apply -c config.yaml -o out/ --execute
293
+
294
+ # Override the L2 instance (defaults to bundled spec_example)
295
+ recon-gen json apply -c config.yaml -o out/ --l2 run/sasquatch_pr.yaml --execute
296
+ ```
297
+
298
+ `json apply --execute` polls async resources (analyses, dashboards) until they reach a terminal state. Resources with the `ManagedBy: recon-gen` tag that aren't in the current output aren't touched — clean those up explicitly:
299
+
300
+ ```bash
301
+ recon-gen json clean # dry-run: list stale tagged resources
302
+ recon-gen json clean --execute # delete them
303
+ ```
304
+
305
+ ### What you get
306
+
307
+ ```
308
+ out/
309
+ theme.json
310
+ datasource.json # demo only (auto-derived)
311
+ investigation-analysis.json
312
+ investigation-dashboard.json
313
+ executives-analysis.json
314
+ executives-dashboard.json
315
+ l1-dashboard-analysis.json
316
+ l1-dashboard-dashboard.json
317
+ l2-flow-tracing-analysis.json
318
+ l2-flow-tracing-dashboard.json
319
+ datasets/
320
+ <deployment_name>-inv-*.json # 5 Investigation datasets
321
+ <deployment_name>-exec-*.json # 2 Executives datasets
322
+ <deployment_name>-l1-*.json # 14 L1 Dashboard datasets
323
+ <deployment_name>-l2ft-*.json # 2 L2 Flow Tracing datasets
324
+ <deployment_name>-*-app-info-*.json # 2 App Info datasets per app (8 total)
325
+ ```
326
+
327
+ `<deployment_name>` comes from `cfg.deployment_name` (required field). Pick distinct values per environment (e.g. `recon-staging` vs `recon-prod`) so multiple deployments can coexist in the same QuickSight account without colliding.
328
+
329
+ ## Demo mode
330
+
331
+ A deterministic demo generator seeds the four apps end-to-end so you can see them work without wiring up real data. Every app feeds two per-prefix base tables — `<db_table_prefix>_transactions` (every money-movement leg) and `<db_table_prefix>_daily_balances` (per-account end-of-day snapshots), where `<db_table_prefix>` is `cfg.db_table_prefix` (required).
332
+
333
+ ```bash
334
+ # Apply schema + seed to your demo database, then generate QuickSight JSON.
335
+ # Requires: demo_database_url + dialect in config.yaml and the matching
336
+ # extra installed (`[demo]` for Postgres, `[demo,demo-oracle]` for Oracle).
337
+ # Per-prefix DDL + seed are emitted at apply time using cfg.db_table_prefix.
338
+ recon-gen schema apply -c config.yaml --execute # tables + matviews
339
+ recon-gen data apply -c config.yaml --execute # 90-day baseline + plants
340
+ recon-gen data refresh -c config.yaml --execute # populate matviews
341
+ recon-gen json apply -c config.yaml -o out/ --execute # JSON + AWS deploy
342
+ recon-gen audit apply -c config.yaml --execute -o report.pdf # regulator-ready PDF (optional)
343
+ ```
344
+
345
+ `schema apply --execute` creates the per-prefix base tables + matviews via `common/l2/schema.py::emit_schema(l2_instance, prefix=cfg.db_table_prefix)`. `data apply --execute` inserts the L2-shape seed data (90-day baseline + every L1 SHOULD-violation plant + the Investigation fanout / volume / chain plants). `data refresh --execute` refreshes every dependent matview in dependency order. `json apply --execute` writes a `datasource.json` derived from the database URL (Type=`POSTGRESQL` or `ORACLE`, dispatched off `dialect`), generates all QuickSight JSON to `out/`, and deploys to AWS. `audit apply --execute` queries the per-prefix L1 invariant matviews and writes a regulator-ready PDF reconciliation report (cover, executive summary, per-invariant violation tables, per-account-day Daily Statement walks, sign-off block, cryptographic provenance fingerprint) — see the [Audit Reconciliation Report handbook](https://chotchki.github.io/recon-gen/handbook/audit/) for the full reference. The `account_type` and `transfer_type` columns discriminate which app a row belongs to. See [`Schema_v6.md`](src/recon_gen/docs/Schema_v6.md) for the full feed contract, canonical type values, metadata key catalog, and ETL examples.
346
+
347
+ **PostgreSQL 17+, Oracle 19c+, or SQLite 3.38+ required** for `schema apply --execute`. PG + Oracle support the SQL/JSON path syntax (`JSON_VALUE`, `JSON_QUERY`, `JSON_EXISTS`) the schema uses for `metadata` JSON columns; SQLite uses the equivalent JSON1 functions (`json_extract`, `json_valid`) routed through the dialect helpers in `common/sql/dialect.py`. The portable subset forbids the Postgres-only `->>` / `->` / `@>` / `?` operators and JSONB; on Oracle, also no named `WINDOW` clause and no `TIMESTAMP WITH TIME ZONE` in PK columns; on SQLite, matviews emit as `CREATE TABLE … AS SELECT` (refreshed by re-CREATE). See `Schema_v6.md` → Forbidden SQL patterns for the full constraint matrix.
348
+
349
+ Datasets are all Direct Query (no SPICE), so seed changes show up immediately after a fresh `data apply --execute` + `data refresh --execute` — no QuickSight-side refresh needed.
350
+
351
+ ### Demo scenarios
352
+
353
+ Two L2 institution YAMLs ship in `tests/l2/`:
354
+
355
+ - **`spec_example.yaml`** — the persona-neutral default fixture. Generic accounts/rails/chains exercising every L2 primitive without naming a specific institution.
356
+ - **`sasquatch_pr.yaml`** — a flavored Sasquatch National Bank persona block carrying the curated demo narrative: SNB control accounts, merchant DDAs (Bigfoot Brews, Sasquatch Sips, etc.), Investigation anchor (Juniper Ridge LLC) with three converging scenarios (12-sender fanout cluster, Cascadia Trust Bank → Juniper anomaly spike, 4-hop layering chain through shell entities).
357
+
358
+ Pass `--l2 tests/l2/sasquatch_pr.yaml` (or your own) to switch the rendered handbook + demo data narrative without touching dashboard code.
359
+
360
+ ## Self-hosted renderer (Dashboards)
361
+
362
+ The four apps render two ways off the same L2 instance. The default is **AWS QuickSight** — `json apply --execute` pushes the JSON resource graph (above). The second is **Dashboards** (formerly "App 2"): a small self-hosted HTMX + d3 page server that reads the same database directly, with no AWS account involved.
363
+
364
+ ```bash
365
+ pip install 'recon-gen[serve]'
366
+ recon-gen dashboards -c config.yaml # one process, all 4 apps + the handbook at /docs
367
+ # → http://127.0.0.1:8765/dashboards
368
+ ```
369
+
370
+ It speaks all three SQL dialects (PostgreSQL / Oracle / SQLite); point `demo_database_url` at any of them. The schema + seed have to already be applied (`schema apply --execute`, `data apply --execute`, `data refresh --execute`) — Dashboards only reads. It's stateless: every GET re-runs the query, filter state round-trips as `?param_X=…` query params (so the URL is the cache key), no auth/sessions — put it behind your own auth front on a network. All browser-side assets (htmx, d3, the filter widgets) ship inside the wheel — it runs offline.
371
+
372
+ Why two renderers: Dashboards is the offline-iteration loop (edit the L2 YAML / dataset SQL, refresh the page — no deploy cycle) and the renderer the in-progress Studio (`recon-gen studio`, the YAML editor + diagram + data-shaping orchestrator + ETL coverage) builds on. A 4-way cross-tool agreement test (`scenario plants ⊆ direct matview SELECT == QuickSight == Dashboards`, `== audit PDF` where it applies) gates the release, so "feature parity with QuickSight, minus the QuickSight bugs" is enforced, not just claimed. Full reference — what ships in the wheel, the maintainer recipes for bumping a vendored asset — in the handbook's *Self-hosting the dashboards* page.
373
+
374
+ ## Theming
375
+
376
+ Theme is declared inline on the L2 institution YAML's `theme:` block. When the L2 instance carries no `theme:` block, `build_theme` returns `None` and AWS QuickSight CLASSIC takes over at deploy (silent-fallback contract). The single `DEFAULT_PRESET` in `common/theme.py` is the in-canvas-accent fallback for apps when their L2 instance declares no theme — no registry, no CLI flag.
377
+
378
+ To customize the demo persona's brand: edit the `theme:` block on `tests/l2/sasquatch_pr.yaml` (or your own L2 YAML). See the `ThemePreset` dataclass in `common/l2/theme.py` for the full field list. Rich-text on the Getting Started sheets resolves the accent color to hex at generate time.
379
+
380
+ ## Project structure
381
+
382
+ ```
383
+ src/recon_gen/
384
+ __main__.py # python -m recon_gen entry point
385
+ cli/ # Click CLI shell — schema | data | json | docs groups
386
+ __init__.py # main + group registration
387
+ schema.py / data.py / json.py / docs.py
388
+ _helpers.py # shared resolve_l2_for_demo / emit_to_target / connect_and_apply
389
+ _app_builders.py # per-app JSON-emit helpers
390
+ common/
391
+ config.py # Config dataclass + YAML/env loader
392
+ models.py # Dataclasses → AWS QuickSight API JSON
393
+ ids.py # Typed ID newtypes (SheetId / VisualId / FilterGroupId / ParameterName)
394
+ theme.py # DEFAULT_PRESET fallback + build_theme(cfg, theme | None)
395
+ persona.py # DemoPersona dataclass — generic skeleton; populated from L2 YAML
396
+ deploy.py # Python deploy (delete-then-create, async waiters)
397
+ cleanup.py # Tag-based cleanup of stale resources
398
+ dataset_contract.py # ColumnSpec / DatasetContract / build_dataset()
399
+ drill.py # Cross-app deep-link URL builder
400
+ clickability.py # Conditional-format helpers
401
+ rich_text.py # XML helpers for SheetTextBox.Content
402
+ probe.py # Playwright walker for deployed-dashboard error surfacing
403
+ tree/ # Typed tree primitives (Phase L). App / Analysis / Dashboard / Sheet,
404
+ # typed Visual subtypes, typed Filter wrappers, Parameter + Filter
405
+ # Controls, Drill actions, Datasets + Columns + Dim/Measure factories,
406
+ # CalcFields. Object-ref cross-references, auto-IDs, emit-time
407
+ # validation. All four apps are tree-built — see CLAUDE.md
408
+ # "Tree pattern" for the L1 / L2 / L3 layer model.
409
+ l2/ # L2 model: primitives, validate, loader, schema, seed,
410
+ # auto_scenario, derived, theme, topology
411
+ sql/dialect.py # Dialect enum (POSTGRES / ORACLE)
412
+ browser/ # Playwright helpers (helpers.py + ScreenshotHarness)
413
+ handbook/ # mkdocs-macros vocabulary + diagrams
414
+ sheets/app_info.py # populate_app_info_sheet — Info canary builder
415
+ apps/
416
+ l1_dashboard/ # 11 sheets, configured by L2 instance
417
+ l2_flow_tracing/ # 4 sheets — Rails / Chains / Templates / Hygiene
418
+ investigation/ # 5 sheets — fanout / anomalies / money trail / account network
419
+ executives/ # 4 sheets — coverage / volume / money moved
420
+ docs/ # Unified mkdocs site source — concepts/, handbook/, walkthroughs/,
421
+ # for-your-role/, scenario/, Schema_v6.md, _diagrams/, _macros/.
422
+ # Renders against any L2 instance via mkdocs-macros + HandbookVocabulary.
423
+ tests/ # Mirror the artifact split: tests/{schema,data,json,docs,unit,e2e}/
424
+ run_tests.sh # Layered test chain runner (unit → db → app2 → deploy → api → browser)
425
+ config.example.yaml
426
+ ```
427
+
428
+ ## Tests
429
+
430
+ ```bash
431
+ ./run_tests.sh up_to=unit # ~20s, no DB / no AWS
432
+ ./run_tests.sh up_to=db # full matrix (13 cells, parallel)
433
+ ./run_tests.sh up_to=db --dialects=pg --targets=lo # pg-container only
434
+ ./run_tests.sh up_to=browser # full chain through Playwright
435
+ ./run_tests.sh up_to=api --variants=sp_pg_aw # single AW cell, API only
436
+ ./run_tests.sh sweep --yes # cleanup orphan AWS resources
437
+ ```
438
+
439
+ The runner enforces ordering — invoking layer N runs layers 1..N-1 first. See `CLAUDE.md::Test sequencing` for the full guide.
440
+
441
+ Coverage:
442
+
443
+ - **Unit / integration**: models, tags, config, CLI, demo determinism + scenario coverage (per-instance SHA256 seed-hash locks), tree primitives + validators, dataset builders, visual builders, filter groups, cross-reference validation (dataset ARNs, filter bindings, visual ID uniqueness, sheet scoping), explanation coverage, schema + seed SQL structure for both Postgres + Oracle.
444
+ - **E2E**: two layers gated by `QS_GEN_E2E=1`.
445
+ - *API layer (boto3)* — resource existence, status, dashboard structure (per-sheet visual counts, parameter / filter-group source-of-truth checks), dataset import health.
446
+ - *Browser layer (Playwright WebKit, headless)* — dashboard loads via pre-authenticated embed URL, sheet tabs, per-sheet visual counts + spot-checked titles, drill-downs, mutual-filter reconciliation tables, date-range filter narrowing, Show-Only-X toggles, Investigation slider + dropdown filters.
447
+
448
+ E2E tunables (env vars): `QS_E2E_PAGE_TIMEOUT`, `QS_E2E_VISUAL_TIMEOUT`, `QS_E2E_USER_ARN`, `QS_E2E_IDENTITY_REGION`. Failure screenshots land in `tests/e2e/screenshots/<app>/` (gitignored).
449
+
450
+ ## Customising
451
+
452
+ ### Change the SQL
453
+
454
+ Edit the dataset builders in `apps/<app>/datasets.py`. Each dataset has a `sql` string and a `DatasetContract` (column name + type list) — unit tests assert the SQL projection matches the contract, so the contract is the safety net when rewriting.
455
+
456
+ The dataset SQL reads from two shared base tables (`<prefix>_transactions`, `<prefix>_daily_balances`) plus the L1 invariant + Investigation matviews. To wire your production data in, ETL into the same shape: see [`Schema_v6.md`](src/recon_gen/docs/Schema_v6.md) for column specifications, the canonical `account_type` / `transfer_type` values, the JSON metadata key catalog, and end-to-end ETL examples.
457
+
458
+ ### Add a visual or tab
459
+
460
+ 1. Open `apps/<app>/app.py` and find the relevant sheet's populator function.
461
+ 2. Place the visual on a layout row: `row.add_kpi(...)`, `row.add_table(...)`, `row.add_bar_chart(...)`, `row.add_sankey(...)`. Pass `title=`, `subtitle=`, and the typed `Dim`/`Measure` slots — the tree validates dataset / column references at emit time.
462
+ 3. Subtitle is required — enforced at construction (`Visual.__post_init__` raises on a blank subtitle), not by a separate test.
463
+ 4. Run `pytest` — typed cross-reference errors fail at the wiring site, not deep in the generated JSON.
464
+
465
+ ### Add a filter
466
+
467
+ Filters push to SQL — a `<<$paramName>>` placeholder in the dataset's CustomSql, not an analysis-level `FilterGroup` (a Phase Y change that converged the QuickSight and self-hosted renderers on the same query-level narrowing). So:
468
+
469
+ 1. **Date filter:** write the dataset SQL with a `{date_filter}` slot in its `WHERE` and call `build_dataset(sql_template, CONTRACT, ..., app2_date_column="<table>.<col>")` — it substitutes the slot per renderer (QuickSight's universal date control narrows QS; the self-hosted renderer gets a `BETWEEN :date_from AND :date_to` bind).
470
+ 2. **Categorical / slider filter:** put `<<$pParamName>>` directly in the `WHERE`; declare the analysis parameter and wire its control (`ParameterDropDownControl` / slider) in `apps/<app>/app.py`. The dataset parameter, the analysis→dataset bridge, and the self-hosted renderer's filter spec are all auto-derived from that one control node.
471
+ 3. `pytest` walks the tree and flags missing references at emit time; the dataset's `DatasetContract` is the safety net when you edit the SQL.
472
+
473
+ (See `CLAUDE.md` → "Filter authoring" for the full pattern. Analysis-level `FilterGroup`s are deprecated for filter intent — kept only for the universal date control and the rare highlight-without-narrowing case.)
474
+
475
+ ### Re-skin
476
+
477
+ Edit your L2 institution YAML's `theme:` block (or copy from `tests/l2/sasquatch_pr.yaml` for a worked example). Keys: `theme_name`, `version_description`, `accent`, `primary_fg`, `link_tint`, `analysis_name_prefix`. See `common/l2/theme.py::ThemePreset` for the full field contract.
478
+
479
+ ### Ask Claude
480
+
481
+ The codebase is intentionally easy to mutate. Ask Claude to add visuals, reshape the layout, adjust filters, update SQL for your schema, or add conditional formatting — it'll edit the Python and re-run tests.