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,289 @@
1
+ """MCP Server — thin shim over dataface.agent_api.
2
+
3
+ Resources: dataface://dashboards, dataface://dashboard/{path},
4
+ dataface://docs/all, dataface://docs/{topic}, dataface://guide/*
5
+
6
+ Tools: validate_dashboard, render_dashboard, execute_query, describe_query,
7
+ schema, schema_search, search_dashboards, query_face, docs,
8
+ describe_dashboard, list_skills, get_skill
9
+
10
+ Usage: dft mcp serve
11
+ """
12
+
13
+ import asyncio
14
+ import json
15
+ import logging
16
+ from pathlib import Path
17
+ from typing import Any, cast
18
+
19
+ from pydantic import AnyUrl, TypeAdapter
20
+
21
+ from dataface._install_hint import install_hint
22
+ from dataface.ai.context import DatafaceAIContext
23
+ from dataface.ai.tool_schemas import (
24
+ DESCRIBE_DASHBOARD,
25
+ DESCRIBE_QUERY,
26
+ DOCS,
27
+ EXECUTE_QUERY,
28
+ GET_SKILL,
29
+ LIST_SKILLS,
30
+ QUERY_FACE,
31
+ RENDER_DASHBOARD,
32
+ SCHEMA,
33
+ SCHEMA_SEARCH,
34
+ SEARCH_DASHBOARDS,
35
+ VALIDATE_DASHBOARD,
36
+ )
37
+ from dataface.ai.tools import dispatch_tool_call
38
+ from dataface.core.execute.adapters import build_adapter_registry
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+ _BASE_RESOURCES = [
43
+ (
44
+ "dataface://dashboards",
45
+ "application/json",
46
+ "Dashboard List",
47
+ "List of all dashboards in the project with metadata",
48
+ ),
49
+ (
50
+ "dataface://docs/all",
51
+ "text/markdown",
52
+ "Dataface YAML Reference (full)",
53
+ "Complete YAML syntax reference — same content as `dft docs all`",
54
+ ),
55
+ (
56
+ "dataface://docs/reference",
57
+ "text/markdown",
58
+ "Dataface YAML Field Reference (generated)",
59
+ "Auto-generated field-level spec for every YAML field. Run `just gen-yaml-reference` to refresh.",
60
+ ),
61
+ (
62
+ "dataface://guide/dashboard-design",
63
+ "text/markdown",
64
+ "Dashboard Design Guide",
65
+ "Design principles for creating effective dashboards. Read this when creating a new dashboard, choosing chart types, or designing layouts for at-a-glance monitoring.",
66
+ ),
67
+ (
68
+ "dataface://guide/report-design",
69
+ "text/markdown",
70
+ "Report Design Guide",
71
+ "Design principles for creating data-driven reports. Read this when creating narrative analyses, answering specific business questions, or writing investigative reports.",
72
+ ),
73
+ (
74
+ "dataface://guide/dashboard-build",
75
+ "text/markdown",
76
+ "Dashboard Build Workflow",
77
+ "Best practices for building dashboards incrementally. Read this before starting work — covers the recommended build-test-iterate workflow, caching, and tool usage patterns.",
78
+ ),
79
+ (
80
+ "dataface://guide/dashboard-review",
81
+ "text/markdown",
82
+ "Dashboard Review",
83
+ "Primary review skill for a Dataface face — runs structural review (YAML + dft validate) and visual review (PNG + vision-model evaluation), synthesizes a ranked findings list.",
84
+ ),
85
+ ]
86
+
87
+
88
+ def _docs_topic_resources() -> list[tuple[str, str, str, str]]:
89
+ """One static resource per H2 in DATAFACE_SYNTAX.md, derived from the live topic index."""
90
+ from dataface.agent_api.docs import docs as docs_verb
91
+
92
+ return [
93
+ (
94
+ f"dataface://docs/{entry.id}",
95
+ "text/markdown",
96
+ f"Dataface YAML Reference — {entry.title}",
97
+ entry.description or f"`dft docs {entry.id}` section",
98
+ )
99
+ for entry in docs_verb().topics
100
+ ]
101
+
102
+
103
+ def _read_resource_content(uri: str, context: DatafaceAIContext) -> str:
104
+ from dataface.agent_api.dashboards import get_dashboard, list_dashboards
105
+ from dataface.agent_api.docs import docs as docs_verb, read_full_text
106
+ from dataface.ai.prompts import load_shared_prompt
107
+
108
+ if uri == "dataface://dashboards":
109
+ return list_dashboards(
110
+ directory=context.dashboards_directory or Path("."),
111
+ recursive=True,
112
+ ).model_dump_json(indent=2, exclude_none=True)
113
+
114
+ if uri == "dataface://docs/all":
115
+ return read_full_text()
116
+
117
+ if uri.startswith("dataface://docs/"):
118
+ slug = uri.replace("dataface://docs/", "")
119
+ result = docs_verb(topic=slug)
120
+ if result.topic is None:
121
+ return json.dumps({"error": f"Unknown topic: {slug}"})
122
+ return result.topic.content
123
+
124
+ if uri.startswith("dataface://guide/"):
125
+ slug = uri.removeprefix("dataface://guide/")
126
+ return load_shared_prompt(slug) or json.dumps(
127
+ {"error": f"Unknown resource: {uri}"}
128
+ )
129
+
130
+ if uri.startswith("dataface://dashboard/"):
131
+ path = uri.replace("dataface://dashboard/", "")
132
+ try:
133
+ resolved_path = context.resolve_dashboard_path(Path(path))
134
+ except ValueError as exc:
135
+ return json.dumps({"error": str(exc)})
136
+ return get_dashboard(path=resolved_path, include_raw=True).model_dump_json(
137
+ indent=2, exclude_none=True
138
+ )
139
+
140
+ return json.dumps({"error": f"Unknown resource: {uri}"})
141
+
142
+
143
+ def _is_domain_error(result: dict[str, Any]) -> bool:
144
+ # Two failure shapes flow through dispatch: handlers emit
145
+ # {"success": false, ...}; dispatch fallbacks emit {"error": str}.
146
+ # The "success is not True" guard keeps a future {"success": True,
147
+ # "error": "warning..."} happy path from being misclassified.
148
+ if result.get("success") is False:
149
+ return True
150
+ if result.get("success") is True:
151
+ return False
152
+ err = result.get("error")
153
+ return isinstance(err, str) and bool(err)
154
+
155
+
156
+ def create_server(context: DatafaceAIContext) -> Any:
157
+ try:
158
+ from mcp.server import Server
159
+ from mcp.types import (
160
+ CallToolResult,
161
+ ContentBlock,
162
+ Resource,
163
+ ResourceTemplate,
164
+ TextContent,
165
+ Tool,
166
+ )
167
+ except ImportError as e:
168
+ raise ImportError(
169
+ "MCP server requires the 'mcp' package. "
170
+ f"Install with: {install_hint('mcp')}"
171
+ ) from e
172
+
173
+ server = Server("dataface")
174
+
175
+ def _uri(value: str) -> AnyUrl:
176
+ return TypeAdapter(AnyUrl).validate_python(value)
177
+
178
+ @server.list_resources()
179
+ async def handle_list_resources() -> list[Resource]:
180
+ return [
181
+ Resource(uri=_uri(u), mimeType=m, name=n, description=d)
182
+ for u, m, n, d in (*_BASE_RESOURCES, *_docs_topic_resources())
183
+ ]
184
+
185
+ @server.list_resource_templates()
186
+ async def handle_list_resource_templates() -> list[ResourceTemplate]:
187
+ return [
188
+ ResourceTemplate(
189
+ uriTemplate="dataface://dashboard/{path}",
190
+ name="Dashboard Content",
191
+ description="YAML content and compiled structure of a specific dashboard",
192
+ mimeType="application/json",
193
+ ),
194
+ ResourceTemplate(
195
+ uriTemplate="dataface://docs/{topic}",
196
+ name="Dataface YAML Reference Section",
197
+ description="One H2 section of DATAFACE_SYNTAX.md (e.g. face, charts, cheatsheet, all)",
198
+ mimeType="text/markdown",
199
+ ),
200
+ ]
201
+
202
+ @server.read_resource()
203
+ async def handle_read_resource(uri: str) -> str:
204
+ return _read_resource_content(str(uri), context=context)
205
+
206
+ @server.list_tools()
207
+ async def handle_list_tools() -> list[Tool]:
208
+ return [
209
+ Tool(
210
+ name=cast(str, t["name"]),
211
+ description=cast(str | None, t["description"]),
212
+ inputSchema=cast(dict[str, Any], t["input_schema"]),
213
+ )
214
+ for t in [
215
+ VALIDATE_DASHBOARD,
216
+ RENDER_DASHBOARD,
217
+ EXECUTE_QUERY,
218
+ DESCRIBE_QUERY,
219
+ SCHEMA,
220
+ SCHEMA_SEARCH,
221
+ SEARCH_DASHBOARDS,
222
+ QUERY_FACE,
223
+ DOCS,
224
+ DESCRIBE_DASHBOARD,
225
+ LIST_SKILLS,
226
+ GET_SKILL,
227
+ ]
228
+ ]
229
+
230
+ @server.call_tool()
231
+ async def handle_call_tool(
232
+ name: str, arguments: dict[str, Any] | None
233
+ ) -> CallToolResult:
234
+ arguments = arguments or {}
235
+ result = dispatch_tool_call(name, arguments, context=context)
236
+ content: list[ContentBlock] = [
237
+ TextContent(type="text", text=json.dumps(result, indent=2))
238
+ ]
239
+ return CallToolResult(content=content, isError=_is_domain_error(result))
240
+
241
+ return server
242
+
243
+
244
+ async def run_server() -> None:
245
+ """Run the MCP server in stdio mode with embedded HTTP server."""
246
+ try:
247
+ from mcp.server.stdio import stdio_server
248
+ except ImportError as e:
249
+ raise ImportError(
250
+ "MCP server requires the 'mcp' package. "
251
+ f"Install with: {install_hint('mcp')}"
252
+ ) from e
253
+
254
+ from dataface.core.execute.adapters import LOCAL_AUTHORING_REGISTRY_KWARGS
255
+ from dataface.core.serve.embedded import build_embedded_server
256
+
257
+ http_server, resolved_http_port = build_embedded_server()
258
+ server = create_server(
259
+ context=DatafaceAIContext(
260
+ adapter_registry=build_adapter_registry(
261
+ Path.cwd(),
262
+ **LOCAL_AUTHORING_REGISTRY_KWARGS,
263
+ ),
264
+ server_port=resolved_http_port,
265
+ )
266
+ )
267
+
268
+ logger.info("Starting Dataface MCP server (stdio mode)")
269
+ http_task = asyncio.create_task(http_server.serve())
270
+
271
+ try:
272
+ async with stdio_server() as (read_stream, write_stream):
273
+ await server.run(
274
+ read_stream,
275
+ write_stream,
276
+ server.create_initialization_options(),
277
+ )
278
+ finally:
279
+ http_server.should_exit = True
280
+ await http_task
281
+
282
+
283
+ def main() -> None:
284
+ """Entry point for running the MCP server."""
285
+ asyncio.run(run_server())
286
+
287
+
288
+ if __name__ == "__main__":
289
+ main()
@@ -0,0 +1,85 @@
1
+ """Project-scoped analyst memory loading and prompt formatting."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import yaml
9
+
10
+ DEFAULT_MEMORY_FILENAME = "memories.yml"
11
+ MAX_PROMPT_MEMORIES = 20
12
+
13
+
14
+ def load_memories(base_dir: Path | None = None) -> list[dict[str, Any]]:
15
+ root = base_dir if base_dir is not None else Path.cwd()
16
+ path = root / DEFAULT_MEMORY_FILENAME
17
+ if not path.exists():
18
+ return []
19
+
20
+ payload = yaml.safe_load(path.read_text()) or {}
21
+ rows = payload.get("memories") if isinstance(payload, dict) else None
22
+ if not isinstance(rows, list):
23
+ return []
24
+
25
+ memories: list[dict[str, Any]] = []
26
+ for row in rows:
27
+ if not isinstance(row, dict):
28
+ continue
29
+ status = str(row.get("status", "active")).strip().lower()
30
+ if status not in {"active", ""}:
31
+ continue
32
+ note = str(row.get("note", "")).strip()
33
+ if not note:
34
+ continue
35
+ memories.append(row)
36
+ memories.sort(
37
+ key=lambda row: (
38
+ str(row.get("updated", "")),
39
+ str(row.get("created", "")),
40
+ str(row.get("type", "")),
41
+ ),
42
+ reverse=True,
43
+ )
44
+ return memories
45
+
46
+
47
+ def _scope_label(memory: dict[str, Any]) -> str:
48
+ table = str(memory.get("table", "")).strip()
49
+ column = str(memory.get("column", "")).strip()
50
+ tables = memory.get("tables")
51
+ if table and column:
52
+ return f"{table}.{column}"
53
+ if table:
54
+ return table
55
+ if isinstance(tables, list):
56
+ joined = ", ".join(str(item).strip() for item in tables if str(item).strip())
57
+ if joined:
58
+ return joined
59
+ scope = str(memory.get("scope", "")).strip()
60
+ return scope
61
+
62
+
63
+ def format_memories_prompt(
64
+ memories: list[dict[str, Any]], *, limit: int = MAX_PROMPT_MEMORIES
65
+ ) -> str:
66
+ if not memories:
67
+ return ""
68
+
69
+ lines = ["## Learned Context"]
70
+ for memory in memories[:limit]:
71
+ memory_type = str(memory.get("type", "memory")).strip() or "memory"
72
+ label = _scope_label(memory)
73
+ note = str(memory.get("note", "")).strip()
74
+ parts = [memory_type]
75
+ if label:
76
+ parts.append(label)
77
+ prefix = " | ".join(parts)
78
+ source = str(memory.get("source", "")).strip()
79
+ confidence = str(memory.get("confidence", "")).strip()
80
+ meta = ", ".join(part for part in [source, confidence] if part)
81
+ if meta:
82
+ lines.append(f"- {prefix}: {note} [{meta}]")
83
+ else:
84
+ lines.append(f"- {prefix}: {note}")
85
+ return "\n".join(lines)
dataface/ai/prompts.py ADDED
@@ -0,0 +1,177 @@
1
+ """Prompt loading and building utilities for AI services.
2
+
3
+ This module provides shared utilities for loading prompts from markdown files
4
+ and building system prompts with context.
5
+
6
+ Shared guides (design principles, workflow, etc.) live in dataface/ai/skills/
7
+ as SKILL.md files — the single source of truth for all AI consumers.
8
+ App-specific prompts (tool instructions, validation loops) live in each app's
9
+ own prompts/ directory.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ from pathlib import Path
16
+
17
+ from dataface.agent_api.skill_render import SkillSurface, render_skill_body
18
+
19
+ SKILLS_DIR = Path(__file__).parent / "skills"
20
+
21
+ _FRONTMATTER_RE = re.compile(r"^---\s*\n.*?\n---\s*\n", re.DOTALL)
22
+
23
+
24
+ def _strip_frontmatter(text: str) -> str:
25
+ """Strip YAML frontmatter (--- ... ---) from a skill file."""
26
+ return _FRONTMATTER_RE.sub("", text, count=1).lstrip("\n")
27
+
28
+
29
+ def load_prompt(prompt_name: str, prompts_dir: Path) -> str:
30
+ """Load a prompt template from markdown file.
31
+
32
+ Args:
33
+ prompt_name: Name of prompt file (without .md extension)
34
+ prompts_dir: Directory containing prompt files
35
+
36
+ Returns:
37
+ Prompt content as string, empty string if not found
38
+
39
+ Example:
40
+ >>> prompt = load_prompt("yaml_generation", Path("/prompts"))
41
+ """
42
+ prompt_file = prompts_dir / f"{prompt_name}.md"
43
+ if prompt_file.exists():
44
+ return prompt_file.read_text()
45
+ return ""
46
+
47
+
48
+ class PromptLoader:
49
+ """Prompt loader with a fixed prompts directory.
50
+
51
+ This class provides a convenient way to load prompts from a specific directory,
52
+ useful when you need to load multiple prompts from the same location.
53
+
54
+ Attributes:
55
+ prompts_dir: The directory containing prompt markdown files
56
+
57
+ Example:
58
+ >>> loader = PromptLoader(Path("/prompts"))
59
+ >>> yaml_prompt = loader.load("yaml_generation")
60
+ >>> dashboard_prompt = loader.load("dashboard_design")
61
+ """
62
+
63
+ def __init__(self, prompts_dir: Path) -> None:
64
+ """Initialize the prompt loader.
65
+
66
+ Args:
67
+ prompts_dir: Directory containing prompt markdown files
68
+ """
69
+ self.prompts_dir = prompts_dir
70
+
71
+ def load(self, prompt_name: str) -> str:
72
+ """Load a prompt template.
73
+
74
+ Args:
75
+ prompt_name: Name of prompt file (without .md extension)
76
+
77
+ Returns:
78
+ Prompt content as string, empty string if not found
79
+ """
80
+ return load_prompt(prompt_name, self.prompts_dir)
81
+
82
+ def load_required(self, prompt_name: str) -> str:
83
+ """Load a prompt template, raising error if not found.
84
+
85
+ Args:
86
+ prompt_name: Name of prompt file (without .md extension)
87
+
88
+ Returns:
89
+ Prompt content as string
90
+
91
+ Raises:
92
+ FileNotFoundError: If prompt file doesn't exist
93
+ """
94
+ prompt_file = self.prompts_dir / f"{prompt_name}.md"
95
+ if not prompt_file.exists():
96
+ raise FileNotFoundError(f"Prompt file not found: {prompt_file}")
97
+ return prompt_file.read_text()
98
+
99
+ def exists(self, prompt_name: str) -> bool:
100
+ """Check if a prompt file exists.
101
+
102
+ Args:
103
+ prompt_name: Name of prompt file (without .md extension)
104
+
105
+ Returns:
106
+ True if prompt file exists
107
+ """
108
+ prompt_file = self.prompts_dir / f"{prompt_name}.md"
109
+ return prompt_file.exists()
110
+
111
+
112
+ def load_shared_prompt(skill_name: str, *, surface: SkillSurface = "mcp") -> str:
113
+ """Load a shared guide from dataface/ai/skills/, rendered for ``surface``.
114
+
115
+ Skills are the single source of truth for design guides and reference
116
+ materials, shared across MCP, playground, cloud, and any other consumer.
117
+ Both call sites today are MCP-vocab (the terminal agent's tool loop and
118
+ the MCP ``dataface://guide/<slug>`` resource handler), so the default
119
+ surface is ``"mcp"``.
120
+
121
+ Args:
122
+ skill_name: The skill directory name under dataface/ai/skills/
123
+ (kebab-case, matches the on-disk directory exactly — e.g.
124
+ "dashboard-build", "dashboard-design", "dashboard-review").
125
+ surface: Which surface's macro vocabulary to render. Default ``"mcp"``.
126
+
127
+ Returns:
128
+ Guide content as string (frontmatter stripped, surface macros
129
+ expanded), empty string if not found.
130
+ """
131
+ skill_file = SKILLS_DIR / skill_name / "SKILL.md"
132
+ if skill_file.exists():
133
+ body = _strip_frontmatter(skill_file.read_text())
134
+ return render_skill_body(body, surface=surface)
135
+ return ""
136
+
137
+
138
+ def build_context_section(
139
+ database_context: str | None = None,
140
+ yaml_context: str | None = None,
141
+ chart_context: str | None = None,
142
+ ) -> str:
143
+ """Build a context section for system prompts.
144
+
145
+ This function creates a formatted context section that can be prepended
146
+ to system prompts. Database context is given highest priority.
147
+
148
+ Args:
149
+ database_context: Optional database schema information
150
+ yaml_context: Optional selected YAML code
151
+ chart_context: Optional chart information
152
+
153
+ Returns:
154
+ Formatted context string, empty if no context provided
155
+
156
+ Example:
157
+ >>> context = build_context_section(
158
+ ... database_context="Tables: users, orders",
159
+ ... yaml_context="title: My Dashboard"
160
+ ... )
161
+ """
162
+ context_parts = []
163
+
164
+ # Database context is critical and should be first
165
+ if database_context:
166
+ context_parts.append(f"## ⚠️ CRITICAL: Database Context\n\n{database_context}\n")
167
+
168
+ if yaml_context:
169
+ context_parts.append(f"## Current YAML Code\n\n```yaml\n{yaml_context}\n```\n")
170
+
171
+ if chart_context:
172
+ context_parts.append(f"## Chart Context\n\n{chart_context}\n")
173
+
174
+ if context_parts:
175
+ return "\n\n".join(context_parts)
176
+
177
+ return ""
@@ -0,0 +1,138 @@
1
+ """Schema context helper — dispatches to the private package when installed.
2
+
3
+ When ``dataface-super-schema`` is installed, its enriched ``get_schema_context``
4
+ is used (cache-first with warm-cache profiler data). Without the private
5
+ package, a bare table list is produced from the dbt adapter + manifest.
6
+
7
+ Import contracts: callers at ``dataface.ai.schema_context.get_schema_context``
8
+ are stable. The private package supplies enrichment; the OSS fallback ensures
9
+ basic functionality without it.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import importlib.util
15
+ import logging
16
+ from pathlib import Path
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ if TYPE_CHECKING:
20
+ from dataface.core.execute.adapters import AdapterRegistry
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ _SUPER_SCHEMA_AVAILABLE = importlib.util.find_spec("dataface_super_schema") is not None
25
+
26
+
27
+ def get_schema_context(
28
+ *,
29
+ adapter_registry: AdapterRegistry,
30
+ source: str | None = None,
31
+ cache_path: Path | None = None,
32
+ ) -> str:
33
+ """Build a schema context string for AI consumption.
34
+
35
+ Delegates to the private package's enriched version when installed.
36
+ Falls back to a bare dbt-only table list otherwise.
37
+ """
38
+ if _SUPER_SCHEMA_AVAILABLE:
39
+ from dataface_super_schema.ai_schema_context import ( # noqa: PLC0415
40
+ get_schema_context as _enriched,
41
+ )
42
+
43
+ return _enriched(
44
+ adapter_registry=adapter_registry,
45
+ source=source,
46
+ cache_path=cache_path,
47
+ )
48
+
49
+ # OSS fallback: dbt-only, no profiler cache.
50
+ return _oss_schema_context(
51
+ adapter_registry=adapter_registry,
52
+ source=source,
53
+ )
54
+
55
+
56
+ def _oss_schema_context(
57
+ *,
58
+ adapter_registry: AdapterRegistry,
59
+ source: str | None = None,
60
+ ) -> str:
61
+ """Bare table list from the dbt adapter + manifest (no profiler cache)."""
62
+ from dataface.core.compile.errors import DatafaceError
63
+ from dataface.core.inspect.resolver import LayeredSchemaResolver
64
+
65
+ resolved_source = (
66
+ source if source is not None else _default_source_name(adapter_registry)
67
+ )
68
+ if resolved_source is None:
69
+ return "No data source configured."
70
+
71
+ source_cfg: dict[str, Any] | None = None
72
+ dialect = "unknown"
73
+ try:
74
+ source_cfg = adapter_registry.resolve_source_config(resolved_source)
75
+ dialect = source_cfg.get("type", "unknown")
76
+ except DatafaceError as e:
77
+ logger.warning("No live DB connection for schema context: %s", e)
78
+ return "No database connection available."
79
+
80
+ project_root = Path(adapter_registry.project_root or ".").resolve()
81
+ resolver = LayeredSchemaResolver(
82
+ cache=None,
83
+ adapter_registry=adapter_registry,
84
+ project_root=project_root,
85
+ )
86
+
87
+ try:
88
+ schemas_resp = resolver.list_schemas(source=resolved_source)
89
+ except Exception as e: # noqa: BLE001 — context provider degrades gracefully
90
+ logger.warning("Failed to list schemas for context: %s", e)
91
+ return f"Error listing schemas: {e}"
92
+
93
+ schemas_branch = (
94
+ schemas_resp.get("sources", {}).get(resolved_source, {}).get("schemas", {})
95
+ )
96
+ lines: list[str] = [f"## Schema: {resolved_source} ({dialect})"]
97
+ for schema_name in schemas_branch:
98
+ try:
99
+ tables_resp = resolver.list_tables(
100
+ source=resolved_source, schema=schema_name
101
+ )
102
+ except Exception as e: # noqa: BLE001 — per-schema best effort
103
+ logger.warning("list_tables failed for %s: %s", schema_name, e)
104
+ continue
105
+ tables_branch = (
106
+ tables_resp.get("sources", {})
107
+ .get(resolved_source, {})
108
+ .get("schemas", {})
109
+ .get(schema_name, {})
110
+ .get("tables", {})
111
+ )
112
+ for table_name, summary in tables_branch.items():
113
+ desc = summary.get("description", "")
114
+ row_count = summary.get("row_count")
115
+ meta_parts = []
116
+ if row_count is not None:
117
+ meta_parts.append(f"{row_count:,} rows")
118
+ if desc:
119
+ meta_parts.append(desc)
120
+ suffix = f" — {'; '.join(meta_parts)}" if meta_parts else ""
121
+ lines.append(f"- {schema_name}.{table_name}{suffix}")
122
+
123
+ if len(lines) == 1:
124
+ return "No tables found in the configured data source."
125
+
126
+ return "\n".join(lines)
127
+
128
+
129
+ def _default_source_name(registry: AdapterRegistry) -> str | None:
130
+ sources = registry.list_sql_sources()
131
+ if not sources:
132
+ return None
133
+ if len(sources) > 1:
134
+ names = ", ".join(item["name"] for item in sources)
135
+ raise ValueError(
136
+ "source is required when multiple SQL sources are configured: " f"{names}"
137
+ )
138
+ return sources[0]["name"]