dataface 0.1.2__py3-none-any.whl

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 (455) hide show
  1. d3_format/__init__.py +14 -0
  2. d3_format/errors.py +19 -0
  3. d3_format/format.py +551 -0
  4. d3_format/spec.py +159 -0
  5. dataface/DATAFACE_SYNTAX.md +1135 -0
  6. dataface/__init__.py +93 -0
  7. dataface/_docs_site.py +20 -0
  8. dataface/_install_hint.py +26 -0
  9. dataface/agent_api/__init__.py +79 -0
  10. dataface/agent_api/_init_templates/__init__.py +0 -0
  11. dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
  12. dataface/agent_api/_init_templates/dataface.yml +15 -0
  13. dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
  14. dataface/agent_api/_init_templates/index.md +24 -0
  15. dataface/agent_api/_paths.py +118 -0
  16. dataface/agent_api/_project_agents_md.py +43 -0
  17. dataface/agent_api/_session_store.py +486 -0
  18. dataface/agent_api/_state.py +28 -0
  19. dataface/agent_api/chat.py +221 -0
  20. dataface/agent_api/dashboards.py +257 -0
  21. dataface/agent_api/describe.py +366 -0
  22. dataface/agent_api/describe_query.py +120 -0
  23. dataface/agent_api/docs/__init__.py +25 -0
  24. dataface/agent_api/docs/_loader.py +292 -0
  25. dataface/agent_api/docs/yaml-reference.md +2757 -0
  26. dataface/agent_api/file_refs.py +118 -0
  27. dataface/agent_api/init.py +126 -0
  28. dataface/agent_api/inspect.py +128 -0
  29. dataface/agent_api/mcp_install.py +170 -0
  30. dataface/agent_api/query.py +274 -0
  31. dataface/agent_api/schema.py +658 -0
  32. dataface/agent_api/schema_search.py +284 -0
  33. dataface/agent_api/search.py +270 -0
  34. dataface/agent_api/skill_install.py +141 -0
  35. dataface/agent_api/skill_render.py +90 -0
  36. dataface/agent_api/skills.py +293 -0
  37. dataface/agent_api/surface_aliases.yaml +128 -0
  38. dataface/agent_api/validate.py +175 -0
  39. dataface/agent_api/validate_query.py +84 -0
  40. dataface/ai/__init__.py +39 -0
  41. dataface/ai/agent.py +139 -0
  42. dataface/ai/context.py +45 -0
  43. dataface/ai/events.py +62 -0
  44. dataface/ai/external_mcp.py +610 -0
  45. dataface/ai/generate_sql.py +96 -0
  46. dataface/ai/llm.py +403 -0
  47. dataface/ai/mcp/__init__.py +51 -0
  48. dataface/ai/mcp/server.py +289 -0
  49. dataface/ai/memories.py +85 -0
  50. dataface/ai/prompts.py +177 -0
  51. dataface/ai/schema_context.py +138 -0
  52. dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
  53. dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
  54. dataface/ai/skills/dashboard-build/SKILL.md +212 -0
  55. dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
  56. dataface/ai/skills/dashboard-design/SKILL.md +182 -0
  57. dataface/ai/skills/dashboard-review/SKILL.md +113 -0
  58. dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
  59. dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
  60. dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
  61. dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
  62. dataface/ai/skills/drill-down-link/SKILL.md +112 -0
  63. dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
  64. dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
  65. dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
  66. dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
  67. dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
  68. dataface/ai/skills/kpi-row/SKILL.md +101 -0
  69. dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
  70. dataface/ai/skills/report-design/SKILL.md +184 -0
  71. dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
  72. dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
  73. dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
  74. dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
  75. dataface/ai/skills/time-series-trend/SKILL.md +93 -0
  76. dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
  77. dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
  78. dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
  79. dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
  80. dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
  81. dataface/ai/tool_schemas.py +132 -0
  82. dataface/ai/tools/__init__.py +312 -0
  83. dataface/ai/yaml_utils.py +57 -0
  84. dataface/cli/__init__.py +3 -0
  85. dataface/cli/_console.py +48 -0
  86. dataface/cli/_error_format.py +83 -0
  87. dataface/cli/_extras.py +190 -0
  88. dataface/cli/_json_output.py +8 -0
  89. dataface/cli/_parsing.py +17 -0
  90. dataface/cli/_version_info.py +56 -0
  91. dataface/cli/commands/__init__.py +3 -0
  92. dataface/cli/commands/_agent_input.py +205 -0
  93. dataface/cli/commands/_agent_server.py +115 -0
  94. dataface/cli/commands/chat.py +645 -0
  95. dataface/cli/commands/describe.py +107 -0
  96. dataface/cli/commands/docs.py +131 -0
  97. dataface/cli/commands/extension.py +179 -0
  98. dataface/cli/commands/init.py +240 -0
  99. dataface/cli/commands/inspect.py +94 -0
  100. dataface/cli/commands/mcp_init.py +167 -0
  101. dataface/cli/commands/query.py +386 -0
  102. dataface/cli/commands/render.py +291 -0
  103. dataface/cli/commands/schema.py +411 -0
  104. dataface/cli/commands/search.py +49 -0
  105. dataface/cli/commands/serve.py +114 -0
  106. dataface/cli/commands/skills.py +133 -0
  107. dataface/cli/commands/skills_init.py +161 -0
  108. dataface/cli/commands/validate.py +63 -0
  109. dataface/cli/main.py +1501 -0
  110. dataface/core/__init__.py +75 -0
  111. dataface/core/compile/__init__.py +244 -0
  112. dataface/core/compile/_jinja_helpers.py +78 -0
  113. dataface/core/compile/channel.py +222 -0
  114. dataface/core/compile/chart_focus.py +101 -0
  115. dataface/core/compile/chart_resolved.py +169 -0
  116. dataface/core/compile/chart_type_detection.py +489 -0
  117. dataface/core/compile/chart_update.py +261 -0
  118. dataface/core/compile/colors.py +64 -0
  119. dataface/core/compile/compiler.py +904 -0
  120. dataface/core/compile/config.py +823 -0
  121. dataface/core/compile/custom_chart_types.py +208 -0
  122. dataface/core/compile/data_table_attachment.py +1287 -0
  123. dataface/core/compile/detect.py +110 -0
  124. dataface/core/compile/errors.py +302 -0
  125. dataface/core/compile/filter_injection.py +319 -0
  126. dataface/core/compile/introspection.py +527 -0
  127. dataface/core/compile/jinja.py +511 -0
  128. dataface/core/compile/labels_env.py +52 -0
  129. dataface/core/compile/markdown.py +154 -0
  130. dataface/core/compile/meta.py +388 -0
  131. dataface/core/compile/models/__init__.py +0 -0
  132. dataface/core/compile/models/chart/__init__.py +0 -0
  133. dataface/core/compile/models/chart/authored.py +2137 -0
  134. dataface/core/compile/models/chart/compiled.py +398 -0
  135. dataface/core/compile/models/config.py +347 -0
  136. dataface/core/compile/models/face/__init__.py +0 -0
  137. dataface/core/compile/models/face/authored.py +659 -0
  138. dataface/core/compile/models/face/compiled.py +522 -0
  139. dataface/core/compile/models/factories.py +201 -0
  140. dataface/core/compile/models/markers.py +40 -0
  141. dataface/core/compile/models/palette.py +36 -0
  142. dataface/core/compile/models/primitives.py +415 -0
  143. dataface/core/compile/models/query/__init__.py +0 -0
  144. dataface/core/compile/models/query/authored.py +246 -0
  145. dataface/core/compile/models/query/compiled.py +710 -0
  146. dataface/core/compile/models/refs.py +137 -0
  147. dataface/core/compile/models/source.py +611 -0
  148. dataface/core/compile/models/style/__init__.py +0 -0
  149. dataface/core/compile/models/style/authored.py +481 -0
  150. dataface/core/compile/models/style/compiled.py +3399 -0
  151. dataface/core/compile/models/style/merged.py +1682 -0
  152. dataface/core/compile/models/theme.py +362 -0
  153. dataface/core/compile/models/variable/__init__.py +0 -0
  154. dataface/core/compile/models/variable/authored.py +254 -0
  155. dataface/core/compile/models/vega_lite/__init__.py +0 -0
  156. dataface/core/compile/models/vega_lite/config.py +510 -0
  157. dataface/core/compile/models/vega_lite/contracts.py +171 -0
  158. dataface/core/compile/normalize_charts.py +494 -0
  159. dataface/core/compile/normalize_layout.py +1000 -0
  160. dataface/core/compile/normalize_queries.py +297 -0
  161. dataface/core/compile/normalize_variables.py +489 -0
  162. dataface/core/compile/normalizer.py +543 -0
  163. dataface/core/compile/palette.py +1100 -0
  164. dataface/core/compile/parameterized.py +658 -0
  165. dataface/core/compile/parser.py +228 -0
  166. dataface/core/compile/schema.py +20 -0
  167. dataface/core/compile/schema_renderers/__init__.py +0 -0
  168. dataface/core/compile/schema_renderers/json_schema.py +163 -0
  169. dataface/core/compile/schema_renderers/prompt.py +152 -0
  170. dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
  171. dataface/core/compile/sizing.py +2126 -0
  172. dataface/core/compile/sources.py +518 -0
  173. dataface/core/compile/sql_authoring_lint.py +56 -0
  174. dataface/core/compile/style_cascade.py +471 -0
  175. dataface/core/compile/typography.py +299 -0
  176. dataface/core/compile/validator.py +301 -0
  177. dataface/core/compile/variables.py +53 -0
  178. dataface/core/compile/vega_config.py +98 -0
  179. dataface/core/compile/vega_lite/__init__.py +6 -0
  180. dataface/core/compile/vega_lite/validation.py +95 -0
  181. dataface/core/compile/yaml_error_formatter.py +838 -0
  182. dataface/core/connections.py +38 -0
  183. dataface/core/dashboard.py +358 -0
  184. dataface/core/defaults/default_config.yml +101 -0
  185. dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
  186. dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
  187. dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
  188. dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
  189. dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
  190. dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
  191. dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
  192. dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
  193. dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
  194. dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
  195. dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
  196. dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
  197. dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
  198. dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
  199. dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
  200. dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
  201. dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
  202. dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
  203. dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
  204. dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
  205. dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
  206. dataface/core/defaults/palettes/sequential/amber.yml +22 -0
  207. dataface/core/defaults/palettes/sequential/blue.yml +22 -0
  208. dataface/core/defaults/palettes/sequential/brown.yml +22 -0
  209. dataface/core/defaults/palettes/sequential/gray.yml +22 -0
  210. dataface/core/defaults/palettes/sequential/green.yml +22 -0
  211. dataface/core/defaults/palettes/sequential/purple.yml +22 -0
  212. dataface/core/defaults/palettes/sequential/rust.yml +22 -0
  213. dataface/core/defaults/palettes/sequential/teal.yml +22 -0
  214. dataface/core/defaults/palettes/tone/negative.yml +32 -0
  215. dataface/core/defaults/palettes/tone/positive.yml +22 -0
  216. dataface/core/defaults/palettes/tone/warning.yml +22 -0
  217. dataface/core/defaults/themes/_base.yaml +786 -0
  218. dataface/core/defaults/themes/bi.yaml +16 -0
  219. dataface/core/defaults/themes/carbong100.yaml +41 -0
  220. dataface/core/defaults/themes/cream.yaml +122 -0
  221. dataface/core/defaults/themes/dark.yaml +40 -0
  222. dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
  223. dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
  224. dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
  225. dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
  226. dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
  227. dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
  228. dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
  229. dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
  230. dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
  231. dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
  232. dataface/core/defaults/themes/editorial.yaml +147 -0
  233. dataface/core/defaults/themes/light.yaml +30 -0
  234. dataface/core/defaults/themes/looker.yaml +17 -0
  235. dataface/core/defaults/themes/stark.yaml +134 -0
  236. dataface/core/errors/__init__.py +67 -0
  237. dataface/core/errors/codes_compile.py +56 -0
  238. dataface/core/errors/codes_execute.py +177 -0
  239. dataface/core/errors/codes_render.py +106 -0
  240. dataface/core/errors/codes_unknown.py +15 -0
  241. dataface/core/errors/hints.py +74 -0
  242. dataface/core/errors/registry.py +42 -0
  243. dataface/core/errors/structured.py +92 -0
  244. dataface/core/execute/__init__.py +91 -0
  245. dataface/core/execute/adapters/__init__.py +49 -0
  246. dataface/core/execute/adapters/adapter_registry.py +400 -0
  247. dataface/core/execute/adapters/base.py +245 -0
  248. dataface/core/execute/adapters/csv_adapter.py +239 -0
  249. dataface/core/execute/adapters/dbt_adapter.py +283 -0
  250. dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
  251. dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
  252. dataface/core/execute/adapters/dbt_utils.py +150 -0
  253. dataface/core/execute/adapters/http_adapter.py +224 -0
  254. dataface/core/execute/adapters/metricflow_adapter.py +94 -0
  255. dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
  256. dataface/core/execute/adapters/sql_adapter.py +710 -0
  257. dataface/core/execute/adapters/values_adapter.py +58 -0
  258. dataface/core/execute/batch.py +744 -0
  259. dataface/core/execute/cache_backend.py +135 -0
  260. dataface/core/execute/cache_keys.py +66 -0
  261. dataface/core/execute/dbt_jinja.py +21 -0
  262. dataface/core/execute/dialects/__init__.py +121 -0
  263. dataface/core/execute/dialects/athena.py +75 -0
  264. dataface/core/execute/dialects/base.py +302 -0
  265. dataface/core/execute/dialects/bigquery.py +38 -0
  266. dataface/core/execute/dialects/databricks.py +68 -0
  267. dataface/core/execute/dialects/duckdb.py +35 -0
  268. dataface/core/execute/dialects/mysql.py +68 -0
  269. dataface/core/execute/dialects/postgres.py +39 -0
  270. dataface/core/execute/dialects/redshift.py +12 -0
  271. dataface/core/execute/dialects/snowflake.py +51 -0
  272. dataface/core/execute/dialects/sqlserver.py +92 -0
  273. dataface/core/execute/duckdb_cache.py +712 -0
  274. dataface/core/execute/duckdb_config.py +26 -0
  275. dataface/core/execute/errors.py +213 -0
  276. dataface/core/execute/executor.py +1249 -0
  277. dataface/core/execute/parallel.py +162 -0
  278. dataface/core/execute/setup_sql.py +58 -0
  279. dataface/core/execute/source_registry.py +72 -0
  280. dataface/core/execute/source_resolver.py +255 -0
  281. dataface/core/execute/sql_guard.py +387 -0
  282. dataface/core/execute/sql_literals.py +199 -0
  283. dataface/core/fonts.py +52 -0
  284. dataface/core/inspect/__init__.py +32 -0
  285. dataface/core/inspect/cache_factory.py +98 -0
  286. dataface/core/inspect/db_types.py +162 -0
  287. dataface/core/inspect/dbt_schema.py +96 -0
  288. dataface/core/inspect/defaults.yml +37 -0
  289. dataface/core/inspect/fanout_risk.py +109 -0
  290. dataface/core/inspect/manifest_utils.py +77 -0
  291. dataface/core/inspect/partials/categorical.yml +40 -0
  292. dataface/core/inspect/partials/date.yml +40 -0
  293. dataface/core/inspect/partials/numeric.yml +55 -0
  294. dataface/core/inspect/partition_types.py +38 -0
  295. dataface/core/inspect/query_validator.py +975 -0
  296. dataface/core/inspect/renderer.py +354 -0
  297. dataface/core/inspect/resolver.py +808 -0
  298. dataface/core/inspect/search.py +461 -0
  299. dataface/core/inspect/sources/__init__.py +32 -0
  300. dataface/core/inspect/sources/dbt.py +738 -0
  301. dataface/core/inspect/sources/duckdb_utils.py +66 -0
  302. dataface/core/inspect/templates/__init__.py +1 -0
  303. dataface/core/inspect/templates/categorical_column.yml +196 -0
  304. dataface/core/inspect/templates/charts.yml +109 -0
  305. dataface/core/inspect/templates/date_column.yml +248 -0
  306. dataface/core/inspect/templates/model.yml +138 -0
  307. dataface/core/inspect/templates/numeric_column.yml +261 -0
  308. dataface/core/inspect/templates/quality.yml +80 -0
  309. dataface/core/inspect/templates/string_column.yml +263 -0
  310. dataface/core/project_roots.py +165 -0
  311. dataface/core/render/__init__.py +87 -0
  312. dataface/core/render/board_links.py +176 -0
  313. dataface/core/render/chart/__init__.py +27 -0
  314. dataface/core/render/chart/arc_attached_table.py +251 -0
  315. dataface/core/render/chart/artifacts.py +16 -0
  316. dataface/core/render/chart/callout.py +225 -0
  317. dataface/core/render/chart/decisions.py +358 -0
  318. dataface/core/render/chart/geo.py +700 -0
  319. dataface/core/render/chart/kpi.py +916 -0
  320. dataface/core/render/chart/labels.py +76 -0
  321. dataface/core/render/chart/pipeline.py +818 -0
  322. dataface/core/render/chart/presentation.py +36 -0
  323. dataface/core/render/chart/profile.py +3438 -0
  324. dataface/core/render/chart/render_single.py +347 -0
  325. dataface/core/render/chart/renderers.py +193 -0
  326. dataface/core/render/chart/rendering.py +565 -0
  327. dataface/core/render/chart/serialization.py +90 -0
  328. dataface/core/render/chart/spark.py +496 -0
  329. dataface/core/render/chart/spark_bar.py +370 -0
  330. dataface/core/render/chart/spec_builders.py +154 -0
  331. dataface/core/render/chart/standard_renderer.py +2645 -0
  332. dataface/core/render/chart/table.py +2957 -0
  333. dataface/core/render/chart/table_support.py +1452 -0
  334. dataface/core/render/chart/tick_values.py +66 -0
  335. dataface/core/render/chart/time_unit_detect.py +809 -0
  336. dataface/core/render/chart/title_overflow.py +157 -0
  337. dataface/core/render/chart/type_inference.py +122 -0
  338. dataface/core/render/chart/validation.py +99 -0
  339. dataface/core/render/chart/vega_lite.py +125 -0
  340. dataface/core/render/chart/vega_lite_types.py +268 -0
  341. dataface/core/render/chart/vl_field_maps.py +346 -0
  342. dataface/core/render/chart_interactivity.py +24 -0
  343. dataface/core/render/control_registry.py +287 -0
  344. dataface/core/render/converters/__init__.py +24 -0
  345. dataface/core/render/converters/chart.py +276 -0
  346. dataface/core/render/converters/html.py +98 -0
  347. dataface/core/render/converters/pdf.py +40 -0
  348. dataface/core/render/converters/png.py +41 -0
  349. dataface/core/render/errors.py +144 -0
  350. dataface/core/render/face_api.py +160 -0
  351. dataface/core/render/faces.py +1194 -0
  352. dataface/core/render/font_measurement.py +48 -0
  353. dataface/core/render/font_support.py +197 -0
  354. dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
  355. dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
  356. dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
  357. dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
  358. dataface/core/render/fonts/InterVariable.ttf +0 -0
  359. dataface/core/render/fonts/InterVariable.woff2 +0 -0
  360. dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
  361. dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
  362. dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
  363. dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
  364. dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
  365. dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
  366. dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
  367. dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
  368. dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
  369. dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
  370. dataface/core/render/fonts/_emoji_font_face.css +43 -0
  371. dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
  372. dataface/core/render/format_utils.py +329 -0
  373. dataface/core/render/geo_defaults.yml +28 -0
  374. dataface/core/render/json_format.py +146 -0
  375. dataface/core/render/layout_sizing.py +865 -0
  376. dataface/core/render/layouts.py +541 -0
  377. dataface/core/render/markdown_defaults.yml +16 -0
  378. dataface/core/render/missing_vars_prompt.py +79 -0
  379. dataface/core/render/placeholder.py +389 -0
  380. dataface/core/render/render_result.py +14 -0
  381. dataface/core/render/renderer.py +467 -0
  382. dataface/core/render/script_embedding.py +16 -0
  383. dataface/core/render/svg_utils.py +212 -0
  384. dataface/core/render/template_loader.py +69 -0
  385. dataface/core/render/templates/controls/_styles.css +606 -0
  386. dataface/core/render/templates/controls/checkbox.html +16 -0
  387. dataface/core/render/templates/controls/date.html +16 -0
  388. dataface/core/render/templates/controls/number.html +19 -0
  389. dataface/core/render/templates/controls/readonly.html +9 -0
  390. dataface/core/render/templates/controls/select.html +21 -0
  391. dataface/core/render/templates/controls/slider.html +22 -0
  392. dataface/core/render/templates/controls/text.html +16 -0
  393. dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
  394. dataface/core/render/templates/scripts/variables.js +976 -0
  395. dataface/core/render/templates/svg/grid_pattern.svg +3 -0
  396. dataface/core/render/templates/svg/styles.css +51 -0
  397. dataface/core/render/terminal.py +311 -0
  398. dataface/core/render/terminal_charts.py +563 -0
  399. dataface/core/render/terminal_defaults.yml +2 -0
  400. dataface/core/render/terminal_layouts.py +299 -0
  401. dataface/core/render/terminal_text.py +31 -0
  402. dataface/core/render/text/__init__.py +1 -0
  403. dataface/core/render/text/case.py +113 -0
  404. dataface/core/render/text_format.py +129 -0
  405. dataface/core/render/utils.py +106 -0
  406. dataface/core/render/variable_controls.py +946 -0
  407. dataface/core/render/variable_input_refinement.py +140 -0
  408. dataface/core/render/warnings/__init__.py +15 -0
  409. dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
  410. dataface/core/render/warnings/base.py +44 -0
  411. dataface/core/render/warnings/fanout_risk.py +15 -0
  412. dataface/core/render/warnings/from_query_diagnostic.py +56 -0
  413. dataface/core/render/warnings/missing_join_predicate.py +13 -0
  414. dataface/core/render/warnings/query_parse_error.py +14 -0
  415. dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
  416. dataface/core/render/warnings/reaggregation.py +14 -0
  417. dataface/core/render/warnings/registry.py +45 -0
  418. dataface/core/render/warnings/suppression.py +46 -0
  419. dataface/core/render/warnings/temporal_single_point.py +63 -0
  420. dataface/core/render/warnings/unreferenced_chart.py +15 -0
  421. dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
  422. dataface/core/render/yaml_format.py +167 -0
  423. dataface/core/resolve_face.py +195 -0
  424. dataface/core/schema/__init__.py +0 -0
  425. dataface/core/schema/guidance.py +151 -0
  426. dataface/core/scoped_paths.py +59 -0
  427. dataface/core/serve/__init__.py +14 -0
  428. dataface/core/serve/bootstrap.py +39 -0
  429. dataface/core/serve/embedded.py +57 -0
  430. dataface/core/serve/port.py +129 -0
  431. dataface/core/serve/server.py +938 -0
  432. dataface/core/serve/templates/__init__.py +0 -0
  433. dataface/core/serve/templates/directory.yml +6 -0
  434. dataface/core/serve/templates/error.html.j2 +217 -0
  435. dataface/core/utils.py +121 -0
  436. dataface/core/validate.py +64 -0
  437. dataface/integrations/__init__.py +0 -0
  438. dataface/integrations/highlighting.py +351 -0
  439. dataface/integrations/markdown.py +537 -0
  440. dataface/py.typed +0 -0
  441. dataface-0.1.2.dist-info/METADATA +375 -0
  442. dataface-0.1.2.dist-info/RECORD +455 -0
  443. dataface-0.1.2.dist-info/WHEEL +4 -0
  444. dataface-0.1.2.dist-info/entry_points.txt +2 -0
  445. dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
  446. mdsvg/__init__.py +168 -0
  447. mdsvg/fonts.py +656 -0
  448. mdsvg/images.py +299 -0
  449. mdsvg/parser.py +629 -0
  450. mdsvg/playground.py +284 -0
  451. mdsvg/py.typed +2 -0
  452. mdsvg/renderer.py +1623 -0
  453. mdsvg/style.py +355 -0
  454. mdsvg/types.py +200 -0
  455. mdsvg/utils.py +86 -0
@@ -0,0 +1,228 @@
1
+ """YAML parsing module.
2
+
3
+ Stage: COMPILE (Step 1 of 4)
4
+ Purpose: Parse YAML strings into AuthoredFace input types.
5
+
6
+ Entry Points:
7
+ - parse_yaml(content: str) -> AuthoredFace
8
+
9
+ Inputs:
10
+ - YAML string (face definition)
11
+
12
+ Outputs:
13
+ - AuthoredFace (input type with optional fields)
14
+
15
+ Dependencies:
16
+ - yaml (PyYAML)
17
+ - .types (AuthoredFace)
18
+
19
+ Errors:
20
+ - ParseError: Invalid YAML syntax (with line numbers, context, suggestions)
21
+
22
+ See also:
23
+ - docs/docs/contributing/architecture.md for the pipeline overview
24
+ - compile/validator.py for the next step
25
+
26
+ Refs #94
27
+ """
28
+
29
+ import re
30
+ from typing import Any
31
+
32
+ import yaml
33
+
34
+ from dataface.core.compile.errors import ParseError
35
+ from dataface.core.compile.models.face.authored import AuthoredFace
36
+ from dataface.core.compile.models.refs import normalize_query_value
37
+ from dataface.core.errors.codes_compile import DF_COMPILE_EXTRA_FIELD
38
+
39
+
40
+ def parse_yaml(content: str) -> AuthoredFace:
41
+ """Parse YAML content into a AuthoredFace object.
42
+
43
+ Stage: COMPILE (Step 1 of 4: Parsing)
44
+
45
+ This is the first step of compilation. It converts a raw YAML string
46
+ into a structured AuthoredFace object. Only basic syntax validation happens
47
+ here - schema validation is the next step.
48
+
49
+ Enhanced error messages include:
50
+ - Line numbers where errors occur
51
+ - YAML context showing the problematic snippet
52
+ - Helpful suggestions ("Did you mean?")
53
+
54
+ Args:
55
+ content: Raw YAML string to parse
56
+
57
+ Returns:
58
+ AuthoredFace object with parsed structure
59
+
60
+ Raises:
61
+ ParseError: If YAML syntax is invalid or parsing fails
62
+
63
+ Example:
64
+ >>> yaml_content = '''
65
+ ... title: My Dataface
66
+ ... queries:
67
+ ... sales: SELECT * FROM sales
68
+ ... charts:
69
+ ... revenue:
70
+ ... query: sales
71
+ ... type: line
72
+ ... rows:
73
+ ... - revenue
74
+ ... '''
75
+ >>> face = parse_yaml(yaml_content)
76
+ >>> face.title
77
+ 'My Dataface'
78
+ """
79
+ # Step 1a: Parse YAML to dict
80
+ try:
81
+ parsed_data = yaml.safe_load(content)
82
+ except yaml.YAMLError as e:
83
+ # Extract line number from YAML error and provide context
84
+ line_num = _extract_line_from_yaml_error(str(e))
85
+ context = _get_yaml_context_for_error(content, line_num) if line_num else None
86
+ suggestion = _get_yaml_parse_suggestion(str(e))
87
+ raise ParseError(
88
+ f"Invalid YAML syntax: {e}",
89
+ line=line_num,
90
+ context=context,
91
+ suggestion=suggestion,
92
+ ) from e
93
+
94
+ # Step 1b: Handle empty content
95
+ if parsed_data is None:
96
+ raise ParseError("Empty YAML document")
97
+
98
+ if not isinstance(parsed_data, dict):
99
+ raise ParseError(f"YAML must be a mapping, got {type(parsed_data).__name__}")
100
+
101
+ # Step 1c: Normalize queries (infer types)
102
+ if "queries" in parsed_data and isinstance(parsed_data["queries"], dict):
103
+ parsed_data["queries"] = _normalize_query_definitions(parsed_data["queries"])
104
+
105
+ # Step 1d: Convert to AuthoredFace with enhanced error messages
106
+ from pydantic import ValidationError as PydanticValidationError
107
+
108
+ try:
109
+ return AuthoredFace(**parsed_data)
110
+ except PydanticValidationError as e:
111
+ # Enhance with line-number context and suggestions first.
112
+ try:
113
+ from dataface.core.compile.yaml_error_formatter import (
114
+ format_validation_error,
115
+ )
116
+
117
+ enhanced_msg = format_validation_error(e, content)
118
+ err = ParseError(enhanced_msg)
119
+ except ParseError:
120
+ raise
121
+ except ImportError:
122
+ err = ParseError(f"Failed to parse face structure: {e}")
123
+
124
+ # Stamp the typed code when the primary failure is an extra field — the
125
+ # enhanced message is preserved; only the code changes from DF-UNKNOWN-INTERNAL.
126
+ pydantic_errors = e.errors()
127
+ if pydantic_errors and pydantic_errors[0].get("type") == "extra_forbidden":
128
+ err.code = DF_COMPILE_EXTRA_FIELD
129
+
130
+ raise err from e
131
+ except (TypeError, ValueError) as e:
132
+ # Other parsing errors (wrong types, invalid values)
133
+ raise ParseError(f"Failed to parse face structure: {e}") from e
134
+
135
+
136
+ def _extract_line_from_yaml_error(error_msg: str) -> int | None:
137
+ """Extract line number from a YAML error message.
138
+
139
+ Args:
140
+ error_msg: The YAML error message
141
+
142
+ Returns:
143
+ Line number or None if not found
144
+ """
145
+ # PyYAML errors often contain "line X"
146
+ match = re.search(r"line\s+(\d+)", error_msg, re.I)
147
+ if match:
148
+ return int(match.group(1))
149
+ return None
150
+
151
+
152
+ def _get_yaml_context_for_error(
153
+ content: str,
154
+ line_num: int,
155
+ context_lines: int = 2,
156
+ ) -> str:
157
+ """Get YAML context around an error line.
158
+
159
+ Args:
160
+ content: Full YAML content
161
+ line_num: Line number of the error (1-indexed)
162
+ context_lines: Number of lines to show before/after
163
+
164
+ Returns:
165
+ Formatted context string
166
+ """
167
+ from dataface.core.compile.yaml_error_formatter import get_yaml_context
168
+
169
+ return get_yaml_context(content, line_num, context_lines)
170
+
171
+
172
+ def _get_yaml_parse_suggestion(error_msg: str) -> str | None:
173
+ """Get a helpful suggestion for a YAML parse error.
174
+
175
+ Args:
176
+ error_msg: The error message
177
+
178
+ Returns:
179
+ Suggestion string or None
180
+ """
181
+ error_lower = error_msg.lower()
182
+
183
+ if "indent" in error_lower:
184
+ return "💡 Check your indentation - YAML requires consistent spacing (typically 2 spaces)"
185
+ elif "expected" in error_lower and "block" in error_lower:
186
+ return "💡 This often happens with incorrect indentation or missing colons"
187
+ elif "mapping" in error_lower:
188
+ return "💡 Check for missing colons after key names or incorrect nesting"
189
+ elif "duplicate" in error_lower:
190
+ return "💡 You have duplicate keys - each key name must be unique at the same level"
191
+ elif "found character" in error_lower:
192
+ return (
193
+ "💡 Check for special characters that need quoting, or invalid YAML syntax"
194
+ )
195
+
196
+ return None
197
+
198
+
199
+ def _normalize_query_definitions(queries: dict[str, Any]) -> dict[str, Any]:
200
+ """Normalize query definitions to consistent format.
201
+
202
+ Converts shorthand query formats to full format:
203
+ - String: "SELECT ..." -> {"type": "sql", "sql": "SELECT ..."}
204
+ - Dict without type: Infers type from keys
205
+
206
+ Delegates to normalize_query_value (models/refs.py) — the shared helper
207
+ used by both this function and AuthoredFace._normalize_queries.
208
+
209
+ Args:
210
+ queries: Raw query definitions from YAML
211
+
212
+ Returns:
213
+ Normalized query definitions with type field
214
+ """
215
+ return {
216
+ name: normalize_query_value(query_def) for name, query_def in queries.items()
217
+ }
218
+
219
+
220
+ _SQL_PREFIX_RE = re.compile(
221
+ r"^\s*(SELECT|WITH|PRAGMA|INSERT|UPDATE|DELETE|CREATE)\b",
222
+ re.IGNORECASE,
223
+ )
224
+
225
+
226
+ def looks_like_sql(s: str) -> bool:
227
+ """Return True when a string starts like a raw SQL statement."""
228
+ return bool(_SQL_PREFIX_RE.match(s))
@@ -0,0 +1,20 @@
1
+ """Schema documentation generation for AI integrations.
2
+
3
+ Delegates to the two-layer IR pipeline:
4
+ introspect() → AuthorableSchema → render_prompt() → str
5
+ """
6
+
7
+ from dataface.core.compile.introspection import introspect
8
+ from dataface.core.compile.schema_renderers.prompt import render_prompt
9
+
10
+
11
+ def get_schema_for_prompt() -> str:
12
+ """Get complete schema documentation for AI prompts."""
13
+ return render_prompt(introspect())
14
+
15
+
16
+ __all__ = ["get_schema_for_prompt"]
17
+
18
+
19
+ if __name__ == "__main__":
20
+ print(get_schema_for_prompt())
File without changes
@@ -0,0 +1,163 @@
1
+ """JSON Schema renderer — Layer 2 of the two-layer schema IR.
2
+
3
+ Converts an AuthorableSchema IR to a draft-07 JSON Schema dict.
4
+ Callers: IDE schema generation, validation tooling.
5
+
6
+ Entry point: render_json_schema(schema: AuthorableSchema) -> dict
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import dataclasses
12
+ from typing import Any
13
+
14
+ from pydantic_core import PydanticUndefined
15
+
16
+ from dataface.core.compile.introspection import AuthorableSchema, SchemaField
17
+
18
+ _PRIMITIVES: dict[str, dict[str, Any]] = {
19
+ "str": {"type": "string"},
20
+ "int": {"type": "integer"},
21
+ "float": {"type": "number"},
22
+ "bool": {"type": "boolean"},
23
+ "None": {"type": "null"},
24
+ }
25
+
26
+
27
+ def _schema_for_token(token: str) -> dict[str, Any]:
28
+ """Map a single non-union type token to a JSON Schema dict."""
29
+ if token in _PRIMITIVES:
30
+ return _PRIMITIVES[token]
31
+ if token.startswith("list["):
32
+ return {"type": "array"}
33
+ if token.startswith("dict["):
34
+ return {"type": "object"}
35
+ return {}
36
+
37
+
38
+ def _type_schema(field: SchemaField) -> dict[str, Any]:
39
+ """Build the JSON Schema type object for a single field."""
40
+ nullable = "None" in field.type_repr
41
+
42
+ extra = [_PRIMITIVES[t] for t in field.extra_union_types if t in _PRIMITIVES]
43
+
44
+ if field.enum_values is not None:
45
+ base: dict[str, Any] = {"enum": field.enum_values}
46
+ branches: list[dict[str, Any]] = [base] + extra
47
+ if nullable:
48
+ branches.append({"type": "null"})
49
+ return {"anyOf": branches} if len(branches) > 1 else branches[0]
50
+
51
+ if field.nested_models:
52
+ refs: list[dict[str, Any]] = [
53
+ {"$ref": f"#/$defs/{n}"} for n in field.nested_models
54
+ ]
55
+ inner: dict[str, Any] = refs[0] if len(refs) == 1 else {"anyOf": refs}
56
+ if field.container == "list":
57
+ arr: dict[str, Any] = {"type": "array", "items": inner}
58
+ parts: list[dict[str, Any]] = [arr] + extra
59
+ if nullable:
60
+ parts.append({"type": "null"})
61
+ return {"anyOf": parts} if len(parts) > 1 else parts[0]
62
+ if field.container == "dict":
63
+ obj: dict[str, Any] = {"type": "object", "additionalProperties": inner}
64
+ parts = [obj] + extra
65
+ if nullable:
66
+ parts.append({"type": "null"})
67
+ return {"anyOf": parts} if len(parts) > 1 else parts[0]
68
+ all_branches: list[dict[str, Any]] = refs + extra
69
+ if nullable:
70
+ all_branches.append({"type": "null"})
71
+ return {"anyOf": all_branches} if len(all_branches) > 1 else all_branches[0]
72
+
73
+ return _map_type_repr(field.type_repr)
74
+
75
+
76
+ def _map_type_repr(type_repr: str) -> dict[str, Any]:
77
+ """Map a type_repr string to a JSON Schema type dict."""
78
+ nullable = " | None" in type_repr
79
+ base = type_repr.replace(" | None", "").strip()
80
+
81
+ if base in _PRIMITIVES:
82
+ schema: dict[str, Any] = _PRIMITIVES[base]
83
+ elif base.startswith("list["):
84
+ schema = {"type": "array"}
85
+ elif base.startswith("dict["):
86
+ schema = {"type": "object"}
87
+ elif " | " in base:
88
+ parts = [p.strip() for p in base.split(" | ")]
89
+ schemas = [s for p in parts if (s := _schema_for_token(p))]
90
+ schema = {"anyOf": schemas} if schemas else {}
91
+ else:
92
+ schema = {}
93
+
94
+ if not nullable or not schema:
95
+ return schema
96
+ if "anyOf" in schema:
97
+ schema = dict(schema)
98
+ schema["anyOf"] = list(schema["anyOf"]) + [{"type": "null"}]
99
+ return schema
100
+ return {"anyOf": [schema, {"type": "null"}]}
101
+
102
+
103
+ def _model_to_def(model_name: str, ir: AuthorableSchema) -> dict[str, Any]:
104
+ """Build a JSON Schema $defs entry for one AuthorableModel."""
105
+ model = ir.models[model_name]
106
+ required = [f.name for f in model.fields if f.required and not f.is_extra_key]
107
+ properties: dict[str, Any] = {}
108
+ additional_properties: dict[str, Any] | None = None
109
+ for field in model.fields:
110
+ prop: dict[str, Any] = _type_schema(field)
111
+ if field.description:
112
+ prop = dict(prop)
113
+ prop["description"] = field.description
114
+ if (
115
+ not field.required
116
+ and field.default is not dataclasses.MISSING
117
+ and field.default is not None
118
+ and field.default is not PydanticUndefined
119
+ ):
120
+ prop = dict(prop)
121
+ prop["default"] = field.default
122
+ if field.is_extra_key:
123
+ additional_properties = prop
124
+ else:
125
+ properties[field.name] = prop
126
+
127
+ defn: dict[str, Any] = {"type": "object", "properties": properties}
128
+ if model.doc:
129
+ defn["description"] = model.doc
130
+ if required:
131
+ defn["required"] = required
132
+ if additional_properties is not None:
133
+ defn["additionalProperties"] = additional_properties
134
+ return defn
135
+
136
+
137
+ def render_json_schema(schema: AuthorableSchema) -> dict[str, Any]:
138
+ """Render an AuthorableSchema IR as a draft-07 JSON Schema.
139
+
140
+ Produces a self-contained schema with the root model's properties
141
+ inlined and all reachable authored models in $defs.
142
+ """
143
+ root = _model_to_def(schema.root, schema)
144
+ defs = {
145
+ name: _model_to_def(name, schema)
146
+ for name in schema.models
147
+ if name != schema.root
148
+ }
149
+
150
+ result: dict[str, Any] = {
151
+ "$schema": "http://json-schema.org/draft-07/schema#",
152
+ "title": schema.root,
153
+ }
154
+ if root.get("description"):
155
+ result["description"] = root["description"]
156
+ result["type"] = "object"
157
+ result["properties"] = root.get("properties", {})
158
+ if root.get("required"):
159
+ result["required"] = root["required"]
160
+ if defs:
161
+ result["$defs"] = defs
162
+
163
+ return result
@@ -0,0 +1,152 @@
1
+ """Prompt renderer — Layer 2 of the two-layer schema IR.
2
+
3
+ Converts an AuthorableSchema IR to a concise text reference for AI prompts.
4
+ Replaces the hand-written schema.get_schema_for_prompt().
5
+
6
+ Entry point: render_prompt(schema: AuthorableSchema) -> str
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+
13
+ from dataface.core.compile.introspection import AuthorableSchema, SchemaField
14
+
15
+ # Source connector configs are leaf models with no cross-references; emit them last.
16
+ _SOURCE_CONFIG_NAMES = [
17
+ "PostgresSourceConfig",
18
+ "SnowflakeSourceConfig",
19
+ "BigQuerySourceConfig",
20
+ "RedshiftSourceConfig",
21
+ "MySQLSourceConfig",
22
+ "DuckDBSourceConfig",
23
+ "CsvSourceConfig",
24
+ "ParquetSourceConfig",
25
+ "JsonSourceConfig",
26
+ "HttpSourceConfig",
27
+ "DbtProfileSourceConfig",
28
+ ]
29
+
30
+
31
+ def _display_name(class_name: str) -> str:
32
+ """Strip 'Authored' prefix and 'Patch' suffix for user-facing model headings."""
33
+ name = class_name
34
+ if name.startswith("Authored"):
35
+ name = name[len("Authored") :]
36
+ if name.endswith("Patch"):
37
+ name = name[: -len("Patch")]
38
+ return name
39
+
40
+
41
+ def _bfs(
42
+ start: str, schema: AuthorableSchema, seen: set[str], order: list[str]
43
+ ) -> None:
44
+ queue = [start]
45
+ while queue:
46
+ name = queue.pop(0)
47
+ if name in seen or name not in schema.models:
48
+ continue
49
+ seen.add(name)
50
+ order.append(name)
51
+ for field in schema.models[name].fields:
52
+ for nested in field.nested_models:
53
+ if nested not in seen:
54
+ queue.append(nested)
55
+
56
+
57
+ def _ordered_model_names(schema: AuthorableSchema) -> list[str]:
58
+ """Return model names in a sensible top-down order.
59
+
60
+ 1. BFS from root (AuthoredFace + all models reachable via type annotations)
61
+ 2. BFS from AuthoredChart (not reachable via AuthoredFace annotations)
62
+ 3. PaginationConfig
63
+ 4. Source connector configs (grouped, leaf-level)
64
+ 5. Any remaining models
65
+ """
66
+ order: list[str] = []
67
+ seen: set[str] = set()
68
+
69
+ _bfs(schema.root, schema, seen, order)
70
+ _bfs("AuthoredChart", schema, seen, order)
71
+ _bfs("PaginationConfig", schema, seen, order)
72
+ for name in _SOURCE_CONFIG_NAMES:
73
+ _bfs(name, schema, seen, order)
74
+ for name in schema.models:
75
+ if name not in seen:
76
+ order.append(name)
77
+
78
+ return order
79
+
80
+
81
+ def _type_cell(field: SchemaField) -> str:
82
+ if field.enum_values is not None:
83
+ return "enum: " + ", ".join(f'"{v}"' for v in field.enum_values)
84
+ t = re.sub(r"\s*\|\s*None\b", "", field.type_repr)
85
+ t = re.sub(r"\bNone\s*\|\s*", "", t)
86
+ for model_name in field.nested_models:
87
+ display = _display_name(model_name)
88
+ # Use word-boundary replacement to avoid matching model_name as a substring
89
+ # of a longer class name (e.g. "BarChart" inside "SparkBarChart").
90
+ t = re.sub(
91
+ r"\b" + re.escape(model_name) + r"\b",
92
+ f"[{display}](#{_anchor(display)})",
93
+ t,
94
+ )
95
+ return t.replace("|", "\\|")
96
+
97
+
98
+ def _anchor(display_name: str) -> str:
99
+ return display_name.lower()
100
+
101
+
102
+ def _render_model(name: str, schema: AuthorableSchema) -> str:
103
+ model = schema.models[name]
104
+ display = _display_name(name)
105
+ lines = [f'<a id="{_anchor(display)}"></a>', f"## {display}"]
106
+ if model.doc:
107
+ lines.append(model.doc)
108
+ lines.append("")
109
+
110
+ required = [f for f in model.fields if f.required]
111
+ optional = [f for f in model.fields if not f.required]
112
+ fields = required + optional
113
+
114
+ show_optional = any(not f.required for f in fields)
115
+
116
+ header = "| Field | Type |"
117
+ sep = "|-------|------|"
118
+ if show_optional:
119
+ header += " Optional |"
120
+ sep += ":--------:|"
121
+ header += " Description |"
122
+ sep += "-------------|"
123
+
124
+ lines.append(header)
125
+ lines.append(sep)
126
+
127
+ for field in fields:
128
+ desc = (field.description or "").replace("|", "\\|")
129
+ row = f"| `{field.name}` | {_type_cell(field)} |"
130
+ if show_optional:
131
+ row += f" {'✓' if not field.required else ''} |"
132
+ row += f" {desc} |"
133
+ lines.append(row)
134
+
135
+ return "\n".join(lines)
136
+
137
+
138
+ def render_prompt(schema: AuthorableSchema) -> str:
139
+ """Render an AuthorableSchema IR as a YAML schema reference table.
140
+
141
+ Suitable for inclusion in AI prompts. Each authored model is rendered as a
142
+ markdown table with required fields first, then optional fields.
143
+ Models are ordered top-down: root first, then referenced sub-models (BFS),
144
+ then chart patch models, then source connector configs.
145
+ """
146
+ order = _ordered_model_names(schema)
147
+ parts = ["# Dataface YAML Schema Reference", ""]
148
+ for i, name in enumerate(order):
149
+ if i > 0:
150
+ parts.append("")
151
+ parts.append(_render_model(name, schema))
152
+ return "\n".join(parts)