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.
- d3_format/__init__.py +14 -0
- d3_format/errors.py +19 -0
- d3_format/format.py +551 -0
- d3_format/spec.py +159 -0
- dataface/DATAFACE_SYNTAX.md +1135 -0
- dataface/__init__.py +93 -0
- dataface/_docs_site.py +20 -0
- dataface/_install_hint.py +26 -0
- dataface/agent_api/__init__.py +79 -0
- dataface/agent_api/_init_templates/__init__.py +0 -0
- dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
- dataface/agent_api/_init_templates/dataface.yml +15 -0
- dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
- dataface/agent_api/_init_templates/index.md +24 -0
- dataface/agent_api/_paths.py +118 -0
- dataface/agent_api/_project_agents_md.py +43 -0
- dataface/agent_api/_session_store.py +486 -0
- dataface/agent_api/_state.py +28 -0
- dataface/agent_api/chat.py +221 -0
- dataface/agent_api/dashboards.py +257 -0
- dataface/agent_api/describe.py +366 -0
- dataface/agent_api/describe_query.py +120 -0
- dataface/agent_api/docs/__init__.py +25 -0
- dataface/agent_api/docs/_loader.py +292 -0
- dataface/agent_api/docs/yaml-reference.md +2757 -0
- dataface/agent_api/file_refs.py +118 -0
- dataface/agent_api/init.py +126 -0
- dataface/agent_api/inspect.py +128 -0
- dataface/agent_api/mcp_install.py +170 -0
- dataface/agent_api/query.py +274 -0
- dataface/agent_api/schema.py +658 -0
- dataface/agent_api/schema_search.py +284 -0
- dataface/agent_api/search.py +270 -0
- dataface/agent_api/skill_install.py +141 -0
- dataface/agent_api/skill_render.py +90 -0
- dataface/agent_api/skills.py +293 -0
- dataface/agent_api/surface_aliases.yaml +128 -0
- dataface/agent_api/validate.py +175 -0
- dataface/agent_api/validate_query.py +84 -0
- dataface/ai/__init__.py +39 -0
- dataface/ai/agent.py +139 -0
- dataface/ai/context.py +45 -0
- dataface/ai/events.py +62 -0
- dataface/ai/external_mcp.py +610 -0
- dataface/ai/generate_sql.py +96 -0
- dataface/ai/llm.py +403 -0
- dataface/ai/mcp/__init__.py +51 -0
- dataface/ai/mcp/server.py +289 -0
- dataface/ai/memories.py +85 -0
- dataface/ai/prompts.py +177 -0
- dataface/ai/schema_context.py +138 -0
- dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
- dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
- dataface/ai/skills/dashboard-build/SKILL.md +212 -0
- dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
- dataface/ai/skills/dashboard-design/SKILL.md +182 -0
- dataface/ai/skills/dashboard-review/SKILL.md +113 -0
- dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
- dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
- dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
- dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
- dataface/ai/skills/drill-down-link/SKILL.md +112 -0
- dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
- dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
- dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
- dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
- dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
- dataface/ai/skills/kpi-row/SKILL.md +101 -0
- dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
- dataface/ai/skills/report-design/SKILL.md +184 -0
- dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
- dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
- dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
- dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
- dataface/ai/skills/time-series-trend/SKILL.md +93 -0
- dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
- dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
- dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
- dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
- dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
- dataface/ai/tool_schemas.py +132 -0
- dataface/ai/tools/__init__.py +312 -0
- dataface/ai/yaml_utils.py +57 -0
- dataface/cli/__init__.py +3 -0
- dataface/cli/_console.py +48 -0
- dataface/cli/_error_format.py +83 -0
- dataface/cli/_extras.py +190 -0
- dataface/cli/_json_output.py +8 -0
- dataface/cli/_parsing.py +17 -0
- dataface/cli/_version_info.py +56 -0
- dataface/cli/commands/__init__.py +3 -0
- dataface/cli/commands/_agent_input.py +205 -0
- dataface/cli/commands/_agent_server.py +115 -0
- dataface/cli/commands/chat.py +645 -0
- dataface/cli/commands/describe.py +107 -0
- dataface/cli/commands/docs.py +131 -0
- dataface/cli/commands/extension.py +179 -0
- dataface/cli/commands/init.py +240 -0
- dataface/cli/commands/inspect.py +94 -0
- dataface/cli/commands/mcp_init.py +167 -0
- dataface/cli/commands/query.py +386 -0
- dataface/cli/commands/render.py +291 -0
- dataface/cli/commands/schema.py +411 -0
- dataface/cli/commands/search.py +49 -0
- dataface/cli/commands/serve.py +114 -0
- dataface/cli/commands/skills.py +133 -0
- dataface/cli/commands/skills_init.py +161 -0
- dataface/cli/commands/validate.py +63 -0
- dataface/cli/main.py +1501 -0
- dataface/core/__init__.py +75 -0
- dataface/core/compile/__init__.py +244 -0
- dataface/core/compile/_jinja_helpers.py +78 -0
- dataface/core/compile/channel.py +222 -0
- dataface/core/compile/chart_focus.py +101 -0
- dataface/core/compile/chart_resolved.py +169 -0
- dataface/core/compile/chart_type_detection.py +489 -0
- dataface/core/compile/chart_update.py +261 -0
- dataface/core/compile/colors.py +64 -0
- dataface/core/compile/compiler.py +904 -0
- dataface/core/compile/config.py +823 -0
- dataface/core/compile/custom_chart_types.py +208 -0
- dataface/core/compile/data_table_attachment.py +1287 -0
- dataface/core/compile/detect.py +110 -0
- dataface/core/compile/errors.py +302 -0
- dataface/core/compile/filter_injection.py +319 -0
- dataface/core/compile/introspection.py +527 -0
- dataface/core/compile/jinja.py +511 -0
- dataface/core/compile/labels_env.py +52 -0
- dataface/core/compile/markdown.py +154 -0
- dataface/core/compile/meta.py +388 -0
- dataface/core/compile/models/__init__.py +0 -0
- dataface/core/compile/models/chart/__init__.py +0 -0
- dataface/core/compile/models/chart/authored.py +2137 -0
- dataface/core/compile/models/chart/compiled.py +398 -0
- dataface/core/compile/models/config.py +347 -0
- dataface/core/compile/models/face/__init__.py +0 -0
- dataface/core/compile/models/face/authored.py +659 -0
- dataface/core/compile/models/face/compiled.py +522 -0
- dataface/core/compile/models/factories.py +201 -0
- dataface/core/compile/models/markers.py +40 -0
- dataface/core/compile/models/palette.py +36 -0
- dataface/core/compile/models/primitives.py +415 -0
- dataface/core/compile/models/query/__init__.py +0 -0
- dataface/core/compile/models/query/authored.py +246 -0
- dataface/core/compile/models/query/compiled.py +710 -0
- dataface/core/compile/models/refs.py +137 -0
- dataface/core/compile/models/source.py +611 -0
- dataface/core/compile/models/style/__init__.py +0 -0
- dataface/core/compile/models/style/authored.py +481 -0
- dataface/core/compile/models/style/compiled.py +3399 -0
- dataface/core/compile/models/style/merged.py +1682 -0
- dataface/core/compile/models/theme.py +362 -0
- dataface/core/compile/models/variable/__init__.py +0 -0
- dataface/core/compile/models/variable/authored.py +254 -0
- dataface/core/compile/models/vega_lite/__init__.py +0 -0
- dataface/core/compile/models/vega_lite/config.py +510 -0
- dataface/core/compile/models/vega_lite/contracts.py +171 -0
- dataface/core/compile/normalize_charts.py +494 -0
- dataface/core/compile/normalize_layout.py +1000 -0
- dataface/core/compile/normalize_queries.py +297 -0
- dataface/core/compile/normalize_variables.py +489 -0
- dataface/core/compile/normalizer.py +543 -0
- dataface/core/compile/palette.py +1100 -0
- dataface/core/compile/parameterized.py +658 -0
- dataface/core/compile/parser.py +228 -0
- dataface/core/compile/schema.py +20 -0
- dataface/core/compile/schema_renderers/__init__.py +0 -0
- dataface/core/compile/schema_renderers/json_schema.py +163 -0
- dataface/core/compile/schema_renderers/prompt.py +152 -0
- dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
- dataface/core/compile/sizing.py +2126 -0
- dataface/core/compile/sources.py +518 -0
- dataface/core/compile/sql_authoring_lint.py +56 -0
- dataface/core/compile/style_cascade.py +471 -0
- dataface/core/compile/typography.py +299 -0
- dataface/core/compile/validator.py +301 -0
- dataface/core/compile/variables.py +53 -0
- dataface/core/compile/vega_config.py +98 -0
- dataface/core/compile/vega_lite/__init__.py +6 -0
- dataface/core/compile/vega_lite/validation.py +95 -0
- dataface/core/compile/yaml_error_formatter.py +838 -0
- dataface/core/connections.py +38 -0
- dataface/core/dashboard.py +358 -0
- dataface/core/defaults/default_config.yml +101 -0
- dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
- dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
- dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
- dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
- dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
- dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
- dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
- dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
- dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
- dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
- dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
- dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
- dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
- dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
- dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
- dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
- dataface/core/defaults/palettes/sequential/amber.yml +22 -0
- dataface/core/defaults/palettes/sequential/blue.yml +22 -0
- dataface/core/defaults/palettes/sequential/brown.yml +22 -0
- dataface/core/defaults/palettes/sequential/gray.yml +22 -0
- dataface/core/defaults/palettes/sequential/green.yml +22 -0
- dataface/core/defaults/palettes/sequential/purple.yml +22 -0
- dataface/core/defaults/palettes/sequential/rust.yml +22 -0
- dataface/core/defaults/palettes/sequential/teal.yml +22 -0
- dataface/core/defaults/palettes/tone/negative.yml +32 -0
- dataface/core/defaults/palettes/tone/positive.yml +22 -0
- dataface/core/defaults/palettes/tone/warning.yml +22 -0
- dataface/core/defaults/themes/_base.yaml +786 -0
- dataface/core/defaults/themes/bi.yaml +16 -0
- dataface/core/defaults/themes/carbong100.yaml +41 -0
- dataface/core/defaults/themes/cream.yaml +122 -0
- dataface/core/defaults/themes/dark.yaml +40 -0
- dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
- dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
- dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
- dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
- dataface/core/defaults/themes/editorial.yaml +147 -0
- dataface/core/defaults/themes/light.yaml +30 -0
- dataface/core/defaults/themes/looker.yaml +17 -0
- dataface/core/defaults/themes/stark.yaml +134 -0
- dataface/core/errors/__init__.py +67 -0
- dataface/core/errors/codes_compile.py +56 -0
- dataface/core/errors/codes_execute.py +177 -0
- dataface/core/errors/codes_render.py +106 -0
- dataface/core/errors/codes_unknown.py +15 -0
- dataface/core/errors/hints.py +74 -0
- dataface/core/errors/registry.py +42 -0
- dataface/core/errors/structured.py +92 -0
- dataface/core/execute/__init__.py +91 -0
- dataface/core/execute/adapters/__init__.py +49 -0
- dataface/core/execute/adapters/adapter_registry.py +400 -0
- dataface/core/execute/adapters/base.py +245 -0
- dataface/core/execute/adapters/csv_adapter.py +239 -0
- dataface/core/execute/adapters/dbt_adapter.py +283 -0
- dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
- dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
- dataface/core/execute/adapters/dbt_utils.py +150 -0
- dataface/core/execute/adapters/http_adapter.py +224 -0
- dataface/core/execute/adapters/metricflow_adapter.py +94 -0
- dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
- dataface/core/execute/adapters/sql_adapter.py +710 -0
- dataface/core/execute/adapters/values_adapter.py +58 -0
- dataface/core/execute/batch.py +744 -0
- dataface/core/execute/cache_backend.py +135 -0
- dataface/core/execute/cache_keys.py +66 -0
- dataface/core/execute/dbt_jinja.py +21 -0
- dataface/core/execute/dialects/__init__.py +121 -0
- dataface/core/execute/dialects/athena.py +75 -0
- dataface/core/execute/dialects/base.py +302 -0
- dataface/core/execute/dialects/bigquery.py +38 -0
- dataface/core/execute/dialects/databricks.py +68 -0
- dataface/core/execute/dialects/duckdb.py +35 -0
- dataface/core/execute/dialects/mysql.py +68 -0
- dataface/core/execute/dialects/postgres.py +39 -0
- dataface/core/execute/dialects/redshift.py +12 -0
- dataface/core/execute/dialects/snowflake.py +51 -0
- dataface/core/execute/dialects/sqlserver.py +92 -0
- dataface/core/execute/duckdb_cache.py +712 -0
- dataface/core/execute/duckdb_config.py +26 -0
- dataface/core/execute/errors.py +213 -0
- dataface/core/execute/executor.py +1249 -0
- dataface/core/execute/parallel.py +162 -0
- dataface/core/execute/setup_sql.py +58 -0
- dataface/core/execute/source_registry.py +72 -0
- dataface/core/execute/source_resolver.py +255 -0
- dataface/core/execute/sql_guard.py +387 -0
- dataface/core/execute/sql_literals.py +199 -0
- dataface/core/fonts.py +52 -0
- dataface/core/inspect/__init__.py +32 -0
- dataface/core/inspect/cache_factory.py +98 -0
- dataface/core/inspect/db_types.py +162 -0
- dataface/core/inspect/dbt_schema.py +96 -0
- dataface/core/inspect/defaults.yml +37 -0
- dataface/core/inspect/fanout_risk.py +109 -0
- dataface/core/inspect/manifest_utils.py +77 -0
- dataface/core/inspect/partials/categorical.yml +40 -0
- dataface/core/inspect/partials/date.yml +40 -0
- dataface/core/inspect/partials/numeric.yml +55 -0
- dataface/core/inspect/partition_types.py +38 -0
- dataface/core/inspect/query_validator.py +975 -0
- dataface/core/inspect/renderer.py +354 -0
- dataface/core/inspect/resolver.py +808 -0
- dataface/core/inspect/search.py +461 -0
- dataface/core/inspect/sources/__init__.py +32 -0
- dataface/core/inspect/sources/dbt.py +738 -0
- dataface/core/inspect/sources/duckdb_utils.py +66 -0
- dataface/core/inspect/templates/__init__.py +1 -0
- dataface/core/inspect/templates/categorical_column.yml +196 -0
- dataface/core/inspect/templates/charts.yml +109 -0
- dataface/core/inspect/templates/date_column.yml +248 -0
- dataface/core/inspect/templates/model.yml +138 -0
- dataface/core/inspect/templates/numeric_column.yml +261 -0
- dataface/core/inspect/templates/quality.yml +80 -0
- dataface/core/inspect/templates/string_column.yml +263 -0
- dataface/core/project_roots.py +165 -0
- dataface/core/render/__init__.py +87 -0
- dataface/core/render/board_links.py +176 -0
- dataface/core/render/chart/__init__.py +27 -0
- dataface/core/render/chart/arc_attached_table.py +251 -0
- dataface/core/render/chart/artifacts.py +16 -0
- dataface/core/render/chart/callout.py +225 -0
- dataface/core/render/chart/decisions.py +358 -0
- dataface/core/render/chart/geo.py +700 -0
- dataface/core/render/chart/kpi.py +916 -0
- dataface/core/render/chart/labels.py +76 -0
- dataface/core/render/chart/pipeline.py +818 -0
- dataface/core/render/chart/presentation.py +36 -0
- dataface/core/render/chart/profile.py +3438 -0
- dataface/core/render/chart/render_single.py +347 -0
- dataface/core/render/chart/renderers.py +193 -0
- dataface/core/render/chart/rendering.py +565 -0
- dataface/core/render/chart/serialization.py +90 -0
- dataface/core/render/chart/spark.py +496 -0
- dataface/core/render/chart/spark_bar.py +370 -0
- dataface/core/render/chart/spec_builders.py +154 -0
- dataface/core/render/chart/standard_renderer.py +2645 -0
- dataface/core/render/chart/table.py +2957 -0
- dataface/core/render/chart/table_support.py +1452 -0
- dataface/core/render/chart/tick_values.py +66 -0
- dataface/core/render/chart/time_unit_detect.py +809 -0
- dataface/core/render/chart/title_overflow.py +157 -0
- dataface/core/render/chart/type_inference.py +122 -0
- dataface/core/render/chart/validation.py +99 -0
- dataface/core/render/chart/vega_lite.py +125 -0
- dataface/core/render/chart/vega_lite_types.py +268 -0
- dataface/core/render/chart/vl_field_maps.py +346 -0
- dataface/core/render/chart_interactivity.py +24 -0
- dataface/core/render/control_registry.py +287 -0
- dataface/core/render/converters/__init__.py +24 -0
- dataface/core/render/converters/chart.py +276 -0
- dataface/core/render/converters/html.py +98 -0
- dataface/core/render/converters/pdf.py +40 -0
- dataface/core/render/converters/png.py +41 -0
- dataface/core/render/errors.py +144 -0
- dataface/core/render/face_api.py +160 -0
- dataface/core/render/faces.py +1194 -0
- dataface/core/render/font_measurement.py +48 -0
- dataface/core/render/font_support.py +197 -0
- dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
- dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
- dataface/core/render/fonts/InterVariable.ttf +0 -0
- dataface/core/render/fonts/InterVariable.woff2 +0 -0
- dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
- dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
- dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
- dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
- dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
- dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
- dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
- dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
- dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
- dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
- dataface/core/render/fonts/_emoji_font_face.css +43 -0
- dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
- dataface/core/render/format_utils.py +329 -0
- dataface/core/render/geo_defaults.yml +28 -0
- dataface/core/render/json_format.py +146 -0
- dataface/core/render/layout_sizing.py +865 -0
- dataface/core/render/layouts.py +541 -0
- dataface/core/render/markdown_defaults.yml +16 -0
- dataface/core/render/missing_vars_prompt.py +79 -0
- dataface/core/render/placeholder.py +389 -0
- dataface/core/render/render_result.py +14 -0
- dataface/core/render/renderer.py +467 -0
- dataface/core/render/script_embedding.py +16 -0
- dataface/core/render/svg_utils.py +212 -0
- dataface/core/render/template_loader.py +69 -0
- dataface/core/render/templates/controls/_styles.css +606 -0
- dataface/core/render/templates/controls/checkbox.html +16 -0
- dataface/core/render/templates/controls/date.html +16 -0
- dataface/core/render/templates/controls/number.html +19 -0
- dataface/core/render/templates/controls/readonly.html +9 -0
- dataface/core/render/templates/controls/select.html +21 -0
- dataface/core/render/templates/controls/slider.html +22 -0
- dataface/core/render/templates/controls/text.html +16 -0
- dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
- dataface/core/render/templates/scripts/variables.js +976 -0
- dataface/core/render/templates/svg/grid_pattern.svg +3 -0
- dataface/core/render/templates/svg/styles.css +51 -0
- dataface/core/render/terminal.py +311 -0
- dataface/core/render/terminal_charts.py +563 -0
- dataface/core/render/terminal_defaults.yml +2 -0
- dataface/core/render/terminal_layouts.py +299 -0
- dataface/core/render/terminal_text.py +31 -0
- dataface/core/render/text/__init__.py +1 -0
- dataface/core/render/text/case.py +113 -0
- dataface/core/render/text_format.py +129 -0
- dataface/core/render/utils.py +106 -0
- dataface/core/render/variable_controls.py +946 -0
- dataface/core/render/variable_input_refinement.py +140 -0
- dataface/core/render/warnings/__init__.py +15 -0
- dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
- dataface/core/render/warnings/base.py +44 -0
- dataface/core/render/warnings/fanout_risk.py +15 -0
- dataface/core/render/warnings/from_query_diagnostic.py +56 -0
- dataface/core/render/warnings/missing_join_predicate.py +13 -0
- dataface/core/render/warnings/query_parse_error.py +14 -0
- dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
- dataface/core/render/warnings/reaggregation.py +14 -0
- dataface/core/render/warnings/registry.py +45 -0
- dataface/core/render/warnings/suppression.py +46 -0
- dataface/core/render/warnings/temporal_single_point.py +63 -0
- dataface/core/render/warnings/unreferenced_chart.py +15 -0
- dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
- dataface/core/render/yaml_format.py +167 -0
- dataface/core/resolve_face.py +195 -0
- dataface/core/schema/__init__.py +0 -0
- dataface/core/schema/guidance.py +151 -0
- dataface/core/scoped_paths.py +59 -0
- dataface/core/serve/__init__.py +14 -0
- dataface/core/serve/bootstrap.py +39 -0
- dataface/core/serve/embedded.py +57 -0
- dataface/core/serve/port.py +129 -0
- dataface/core/serve/server.py +938 -0
- dataface/core/serve/templates/__init__.py +0 -0
- dataface/core/serve/templates/directory.yml +6 -0
- dataface/core/serve/templates/error.html.j2 +217 -0
- dataface/core/utils.py +121 -0
- dataface/core/validate.py +64 -0
- dataface/integrations/__init__.py +0 -0
- dataface/integrations/highlighting.py +351 -0
- dataface/integrations/markdown.py +537 -0
- dataface/py.typed +0 -0
- dataface-0.1.2.dist-info/METADATA +375 -0
- dataface-0.1.2.dist-info/RECORD +455 -0
- dataface-0.1.2.dist-info/WHEEL +4 -0
- dataface-0.1.2.dist-info/entry_points.txt +2 -0
- dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
- mdsvg/__init__.py +168 -0
- mdsvg/fonts.py +656 -0
- mdsvg/images.py +299 -0
- mdsvg/parser.py +629 -0
- mdsvg/playground.py +284 -0
- mdsvg/py.typed +2 -0
- mdsvg/renderer.py +1623 -0
- mdsvg/style.py +355 -0
- mdsvg/types.py +200 -0
- mdsvg/utils.py +86 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
"""Chart type auto-detection based on data structure.
|
|
2
|
+
|
|
3
|
+
Stage: RENDER (Runtime)
|
|
4
|
+
Purpose: Automatically select the most appropriate chart type based on data.
|
|
5
|
+
|
|
6
|
+
Entry Points:
|
|
7
|
+
- detect_chart_type(data, x_field, y_field, color_field, column_descriptions) -> (str, str)
|
|
8
|
+
- detect_chart_type_full(...) -> DetectionResult (includes field suggestions)
|
|
9
|
+
|
|
10
|
+
Detection considers:
|
|
11
|
+
- Data types: numeric, categorical, temporal, identifier
|
|
12
|
+
- Column count: chartable columns (excluding identifiers)
|
|
13
|
+
- Cardinality: distinct values in each column
|
|
14
|
+
- Column names: hints from naming conventions
|
|
15
|
+
- Database types: cursor.description when available (trusts DB over value parsing)
|
|
16
|
+
|
|
17
|
+
Dependencies:
|
|
18
|
+
- datetime (for temporal detection)
|
|
19
|
+
- re (for pattern matching)
|
|
20
|
+
|
|
21
|
+
See also:
|
|
22
|
+
- types.py: Chart type definitions
|
|
23
|
+
- render/chart_decisions.py: Uses db_type_to_dtype for DB type mapping
|
|
24
|
+
- render/vega_lite.py: Uses detected chart type for rendering
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import re
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from datetime import datetime
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
# Chart type constants
|
|
33
|
+
CHART_TYPE_KPI = "kpi"
|
|
34
|
+
CHART_TYPE_LINE = "line"
|
|
35
|
+
CHART_TYPE_BAR = "bar"
|
|
36
|
+
CHART_TYPE_SCATTER = "scatter"
|
|
37
|
+
CHART_TYPE_TABLE = "table"
|
|
38
|
+
CHART_TYPE_HEATMAP = "heatmap"
|
|
39
|
+
CHART_TYPE_AREA = "area"
|
|
40
|
+
CHART_TYPE_PIE = "pie"
|
|
41
|
+
|
|
42
|
+
# Identifier column name patterns
|
|
43
|
+
_IDENTIFIER_PATTERNS = re.compile(
|
|
44
|
+
r"(^id$|_id$|_key$|_pk$|^pk$|^uuid$|^guid$|_uuid$|_guid$|^sk$|_sk$)",
|
|
45
|
+
re.IGNORECASE,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# DB types that are always identifiers regardless of name
|
|
49
|
+
_IDENTIFIER_DB_TYPES = frozenset({"UUID", "SERIAL", "BIGSERIAL", "SMALLSERIAL"})
|
|
50
|
+
|
|
51
|
+
# Chartable column threshold — above this, default to table
|
|
52
|
+
_MAX_CHARTABLE_COLUMNS = 8
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class DetectionResult:
|
|
57
|
+
"""Result of chart type auto-detection, including field suggestions."""
|
|
58
|
+
|
|
59
|
+
chart_type: str
|
|
60
|
+
reasoning: str
|
|
61
|
+
x: str | None = None
|
|
62
|
+
y: str | list[str] | None = None
|
|
63
|
+
color: str | None = None
|
|
64
|
+
value: str | None = None
|
|
65
|
+
theta: str | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def detect_chart_type(
|
|
69
|
+
data: list[dict[str, Any]],
|
|
70
|
+
x_field: str | None = None,
|
|
71
|
+
y_field: str | None = None,
|
|
72
|
+
color_field: str | None = None,
|
|
73
|
+
value_field: str | None = None,
|
|
74
|
+
column_descriptions: dict[str, tuple] | None = None,
|
|
75
|
+
) -> tuple[str, str]:
|
|
76
|
+
"""Detect the most appropriate chart type based on data structure.
|
|
77
|
+
|
|
78
|
+
Returns (chart_type, reasoning).
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
data: Query result data as list of dicts
|
|
82
|
+
x_field: Optional x-axis field (if specified by user)
|
|
83
|
+
y_field: Optional y-axis field (if specified by user)
|
|
84
|
+
color_field: Optional color field (if specified by user)
|
|
85
|
+
value_field: Optional KPI value column reference (if specified by user)
|
|
86
|
+
column_descriptions: Optional PEP 249 cursor.description tuples keyed
|
|
87
|
+
by column name. Used for DB-type-aware classification.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Tuple of (chart_type, reasoning)
|
|
91
|
+
"""
|
|
92
|
+
result = detect_chart_type_full(
|
|
93
|
+
data, x_field, y_field, color_field, value_field, column_descriptions
|
|
94
|
+
)
|
|
95
|
+
return result.chart_type, result.reasoning
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def detect_chart_type_full(
|
|
99
|
+
data: list[dict[str, Any]],
|
|
100
|
+
x_field: str | None = None,
|
|
101
|
+
y_field: str | None = None,
|
|
102
|
+
color_field: str | None = None,
|
|
103
|
+
value_field: str | None = None,
|
|
104
|
+
column_descriptions: dict[str, tuple] | None = None,
|
|
105
|
+
) -> DetectionResult:
|
|
106
|
+
"""Full detection returning chart type AND field suggestions.
|
|
107
|
+
|
|
108
|
+
This is the richer API that also suggests x/y/color/value/theta fields.
|
|
109
|
+
The simpler detect_chart_type() wraps it for callers that only need
|
|
110
|
+
the chart type and reasoning.
|
|
111
|
+
"""
|
|
112
|
+
if not data:
|
|
113
|
+
return DetectionResult(CHART_TYPE_TABLE, "Empty data defaults to table display")
|
|
114
|
+
|
|
115
|
+
columns = list(data[0].keys())
|
|
116
|
+
column_types = _analyze_column_types(data, columns, column_descriptions)
|
|
117
|
+
column_cardinalities = _analyze_cardinality(data, columns)
|
|
118
|
+
|
|
119
|
+
identifier_cols = [c for c, t in column_types.items() if t == "identifier"]
|
|
120
|
+
numeric_cols = [c for c, t in column_types.items() if t == "numeric"]
|
|
121
|
+
temporal_cols = [c for c, t in column_types.items() if t == "temporal"]
|
|
122
|
+
categorical_cols = [c for c, t in column_types.items() if t == "categorical"]
|
|
123
|
+
|
|
124
|
+
chartable_cols = [c for c in columns if c not in identifier_cols]
|
|
125
|
+
|
|
126
|
+
# ════════════════════════════════════════════════════════════════════
|
|
127
|
+
# HEURISTIC 0: Many chartable columns → Table
|
|
128
|
+
# ════════════════════════════════════════════════════════════════════
|
|
129
|
+
if len(chartable_cols) > _MAX_CHARTABLE_COLUMNS:
|
|
130
|
+
return DetectionResult(
|
|
131
|
+
CHART_TYPE_TABLE,
|
|
132
|
+
f"Many chartable columns ({len(chartable_cols)}) suggests tabular display",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# ════════════════════════════════════════════════════════════════════
|
|
136
|
+
# HEURISTIC 1: Single row with single numeric value → KPI
|
|
137
|
+
# ════════════════════════════════════════════════════════════════════
|
|
138
|
+
if len(data) == 1 and len(numeric_cols) == 1 and len(chartable_cols) <= 2:
|
|
139
|
+
return DetectionResult(
|
|
140
|
+
CHART_TYPE_KPI,
|
|
141
|
+
"Single numeric value suggests a KPI/big number display",
|
|
142
|
+
value=numeric_cols[0],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if value_field and len(data) == 1:
|
|
146
|
+
return DetectionResult(
|
|
147
|
+
CHART_TYPE_KPI,
|
|
148
|
+
f"Value field '{value_field}' with single row suggests KPI",
|
|
149
|
+
value=value_field,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# ════════════════════════════════════════════════════════════════════
|
|
153
|
+
# HEURISTIC 2: User specified both x and y fields
|
|
154
|
+
# ════════════════════════════════════════════════════════════════════
|
|
155
|
+
if x_field and y_field:
|
|
156
|
+
x_type = column_types.get(x_field, "categorical")
|
|
157
|
+
y_type = column_types.get(y_field, "numeric")
|
|
158
|
+
|
|
159
|
+
if x_type == "temporal" and y_type == "numeric":
|
|
160
|
+
return DetectionResult(
|
|
161
|
+
CHART_TYPE_LINE,
|
|
162
|
+
f"Temporal x-axis ({x_field}) with numeric y-axis ({y_field}) suggests a line chart",
|
|
163
|
+
x=x_field,
|
|
164
|
+
y=y_field,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if x_type == "numeric" and y_type == "numeric":
|
|
168
|
+
return DetectionResult(
|
|
169
|
+
CHART_TYPE_SCATTER,
|
|
170
|
+
f"Two numeric columns ({x_field}, {y_field}) suggests a scatter plot",
|
|
171
|
+
x=x_field,
|
|
172
|
+
y=y_field,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if x_type in ("categorical", "identifier") and y_type == "numeric":
|
|
176
|
+
return DetectionResult(
|
|
177
|
+
CHART_TYPE_BAR,
|
|
178
|
+
f"Categorical x-axis ({x_field}) with numeric y-axis ({y_field}) suggests a bar chart",
|
|
179
|
+
x=x_field,
|
|
180
|
+
y=y_field,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# ════════════════════════════════════════════════════════════════════
|
|
184
|
+
# HEURISTIC 3: Infer from data structure when fields not specified
|
|
185
|
+
# ════════════════════════════════════════════════════════════════════
|
|
186
|
+
|
|
187
|
+
# Temporal + numeric → line chart (always line, never area)
|
|
188
|
+
if len(temporal_cols) >= 1 and len(numeric_cols) >= 1:
|
|
189
|
+
x_pick = temporal_cols[0]
|
|
190
|
+
y_pick: str | list[str] = (
|
|
191
|
+
numeric_cols[0] if len(numeric_cols) == 1 else numeric_cols
|
|
192
|
+
)
|
|
193
|
+
return DetectionResult(
|
|
194
|
+
CHART_TYPE_LINE,
|
|
195
|
+
f"Temporal column ({x_pick}) with numeric data suggests a line chart",
|
|
196
|
+
x=x_pick,
|
|
197
|
+
y=y_pick,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Temporal + categorical + numeric → heatmap (e.g. weekday × hour)
|
|
201
|
+
if (
|
|
202
|
+
len(temporal_cols) >= 1
|
|
203
|
+
and len(categorical_cols) >= 1
|
|
204
|
+
and len(numeric_cols) >= 1
|
|
205
|
+
):
|
|
206
|
+
t_card = column_cardinalities.get(temporal_cols[0], 0)
|
|
207
|
+
c_card = column_cardinalities.get(categorical_cols[0], 0)
|
|
208
|
+
if t_card <= 12 and c_card <= 12:
|
|
209
|
+
return DetectionResult(
|
|
210
|
+
CHART_TYPE_HEATMAP,
|
|
211
|
+
f"Temporal ({temporal_cols[0]}) x categorical ({categorical_cols[0]}) with measure suggests a heatmap",
|
|
212
|
+
x=temporal_cols[0],
|
|
213
|
+
y=categorical_cols[0],
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# 2+ categorical + numeric → heatmap (both low cardinality) or bar
|
|
217
|
+
if len(categorical_cols) >= 2 and len(numeric_cols) >= 1:
|
|
218
|
+
cat1_card = column_cardinalities.get(categorical_cols[0], 0)
|
|
219
|
+
cat2_card = column_cardinalities.get(categorical_cols[1], 0)
|
|
220
|
+
if cat1_card <= 10 and cat2_card <= 10:
|
|
221
|
+
return DetectionResult(
|
|
222
|
+
CHART_TYPE_HEATMAP,
|
|
223
|
+
f"Two categorical dimensions ({categorical_cols[0]}, {categorical_cols[1]}) with numeric measure suggests a heatmap",
|
|
224
|
+
x=categorical_cols[0],
|
|
225
|
+
y=categorical_cols[1],
|
|
226
|
+
)
|
|
227
|
+
return DetectionResult(
|
|
228
|
+
CHART_TYPE_BAR,
|
|
229
|
+
"Multiple categories with numeric measure suggests a bar chart",
|
|
230
|
+
x=categorical_cols[0],
|
|
231
|
+
y=numeric_cols[0],
|
|
232
|
+
color=categorical_cols[1],
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# 1 categorical + 1+ numeric → pie (3-6 values) or bar
|
|
236
|
+
if len(categorical_cols) == 1 and len(numeric_cols) >= 1:
|
|
237
|
+
cardinality = column_cardinalities.get(categorical_cols[0], 0)
|
|
238
|
+
if 3 <= cardinality <= 6:
|
|
239
|
+
return DetectionResult(
|
|
240
|
+
CHART_TYPE_PIE,
|
|
241
|
+
f"Low cardinality categorical ({categorical_cols[0]}) with {cardinality} values suggests a pie chart",
|
|
242
|
+
theta=numeric_cols[0],
|
|
243
|
+
color=categorical_cols[0],
|
|
244
|
+
)
|
|
245
|
+
if cardinality <= 20:
|
|
246
|
+
return DetectionResult(
|
|
247
|
+
CHART_TYPE_BAR,
|
|
248
|
+
f"Categorical column ({categorical_cols[0]}) with numeric data suggests a bar chart",
|
|
249
|
+
x=categorical_cols[0],
|
|
250
|
+
y=numeric_cols[0],
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# 2+ numeric, no temporal → scatter
|
|
254
|
+
if len(numeric_cols) >= 2 and len(temporal_cols) == 0:
|
|
255
|
+
return DetectionResult(
|
|
256
|
+
CHART_TYPE_SCATTER,
|
|
257
|
+
f"Multiple numeric columns ({', '.join(numeric_cols[:2])}) suggests a scatter plot",
|
|
258
|
+
x=numeric_cols[0],
|
|
259
|
+
y=numeric_cols[1],
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# ════════════════════════════════════════════════════════════════════
|
|
263
|
+
# FALLBACK
|
|
264
|
+
# ════════════════════════════════════════════════════════════════════
|
|
265
|
+
if len(numeric_cols) >= 1:
|
|
266
|
+
x_cand = categorical_cols[0] if categorical_cols else None
|
|
267
|
+
return DetectionResult(
|
|
268
|
+
CHART_TYPE_BAR,
|
|
269
|
+
"Default to bar chart for numeric data",
|
|
270
|
+
x=x_cand,
|
|
271
|
+
y=numeric_cols[0],
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return DetectionResult(
|
|
275
|
+
CHART_TYPE_TABLE,
|
|
276
|
+
"Unable to determine optimal chart type, defaulting to table",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _analyze_column_types(
|
|
281
|
+
data: list[dict[str, Any]],
|
|
282
|
+
columns: list[str],
|
|
283
|
+
column_descriptions: dict[str, tuple] | None = None,
|
|
284
|
+
) -> dict[str, str]:
|
|
285
|
+
"""Analyze and classify column types.
|
|
286
|
+
|
|
287
|
+
Categories: numeric, temporal, categorical, identifier.
|
|
288
|
+
|
|
289
|
+
When column_descriptions are available, database types take priority
|
|
290
|
+
over value-based heuristics (e.g. VARCHAR is always categorical even
|
|
291
|
+
if values look numeric like zip codes).
|
|
292
|
+
"""
|
|
293
|
+
column_types: dict[str, str] = {}
|
|
294
|
+
row_count = len(data)
|
|
295
|
+
|
|
296
|
+
for col in columns:
|
|
297
|
+
sample_values = [row.get(col) for row in data[:100] if row.get(col) is not None]
|
|
298
|
+
|
|
299
|
+
# Layer 1: Use database type if available
|
|
300
|
+
db_dtype = None
|
|
301
|
+
db_type_str = None
|
|
302
|
+
if column_descriptions and col in column_descriptions:
|
|
303
|
+
desc = column_descriptions[col]
|
|
304
|
+
if desc and len(desc) > 1 and desc[1] is not None:
|
|
305
|
+
db_type_str = str(desc[1])
|
|
306
|
+
from dataface.core.render.chart.decisions import db_type_to_dtype
|
|
307
|
+
|
|
308
|
+
db_dtype = db_type_to_dtype(db_type_str)
|
|
309
|
+
|
|
310
|
+
# Layer 2: Value-based classification (fallback)
|
|
311
|
+
if db_dtype:
|
|
312
|
+
base_type = db_dtype
|
|
313
|
+
elif not sample_values:
|
|
314
|
+
base_type = "categorical"
|
|
315
|
+
else:
|
|
316
|
+
base_type = classify_column_type(col, sample_values, db_type_str)
|
|
317
|
+
|
|
318
|
+
# Layer 3: Identifier detection (overrides numeric/categorical)
|
|
319
|
+
if _is_identifier(col, base_type, sample_values, row_count, db_type_str):
|
|
320
|
+
column_types[col] = "identifier"
|
|
321
|
+
else:
|
|
322
|
+
column_types[col] = base_type
|
|
323
|
+
|
|
324
|
+
return column_types
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _is_identifier(
|
|
328
|
+
col_name: str,
|
|
329
|
+
base_type: str,
|
|
330
|
+
sample_values: list[Any],
|
|
331
|
+
row_count: int,
|
|
332
|
+
db_type_str: str | None = None,
|
|
333
|
+
) -> bool:
|
|
334
|
+
"""Detect if a column is an identifier (primary key, UUID, etc.).
|
|
335
|
+
|
|
336
|
+
Identifiers are excluded from chart axis candidates.
|
|
337
|
+
"""
|
|
338
|
+
if db_type_str:
|
|
339
|
+
upper = db_type_str.upper().split("(")[0].strip()
|
|
340
|
+
if upper in _IDENTIFIER_DB_TYPES:
|
|
341
|
+
return True
|
|
342
|
+
|
|
343
|
+
if not _IDENTIFIER_PATTERNS.search(col_name):
|
|
344
|
+
return False
|
|
345
|
+
|
|
346
|
+
if not sample_values or base_type not in ("numeric", "categorical"):
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
distinct = len({str(v) for v in sample_values})
|
|
350
|
+
sample_size = min(len(sample_values), row_count)
|
|
351
|
+
if sample_size == 0:
|
|
352
|
+
return False
|
|
353
|
+
|
|
354
|
+
return distinct / sample_size > 0.9
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def classify_column_type(
|
|
358
|
+
column_name: str,
|
|
359
|
+
sample_values: list[Any],
|
|
360
|
+
db_type_str: str | None = None,
|
|
361
|
+
) -> str:
|
|
362
|
+
"""Classify a single column's type based on its values and name.
|
|
363
|
+
|
|
364
|
+
When db_type_str indicates VARCHAR/TEXT, string values are NOT parsed
|
|
365
|
+
as numbers — the database type is trusted.
|
|
366
|
+
|
|
367
|
+
Returns: "numeric", "temporal", or "categorical"
|
|
368
|
+
"""
|
|
369
|
+
is_db_string = False
|
|
370
|
+
if db_type_str:
|
|
371
|
+
upper = db_type_str.upper().split("(")[0].strip()
|
|
372
|
+
if upper.startswith("VARCHAR") or upper in ("TEXT", "CHAR", "STRING", "ENUM"):
|
|
373
|
+
is_db_string = True
|
|
374
|
+
|
|
375
|
+
name_lower = column_name.lower()
|
|
376
|
+
|
|
377
|
+
temporal_patterns = [
|
|
378
|
+
r"date",
|
|
379
|
+
r"time",
|
|
380
|
+
r"timestamp",
|
|
381
|
+
r"created",
|
|
382
|
+
r"updated",
|
|
383
|
+
r"_at$",
|
|
384
|
+
r"day",
|
|
385
|
+
r"week",
|
|
386
|
+
r"month",
|
|
387
|
+
r"year",
|
|
388
|
+
r"quarter",
|
|
389
|
+
r"period",
|
|
390
|
+
]
|
|
391
|
+
for pattern in temporal_patterns:
|
|
392
|
+
if re.search(pattern, name_lower) and any(
|
|
393
|
+
_is_temporal_value(v) for v in sample_values[:5]
|
|
394
|
+
):
|
|
395
|
+
return "temporal"
|
|
396
|
+
|
|
397
|
+
numeric_count = 0
|
|
398
|
+
temporal_count = 0
|
|
399
|
+
categorical_count = 0
|
|
400
|
+
|
|
401
|
+
for val in sample_values:
|
|
402
|
+
if isinstance(val, bool):
|
|
403
|
+
categorical_count += 1
|
|
404
|
+
elif isinstance(val, (int, float)):
|
|
405
|
+
numeric_count += 1
|
|
406
|
+
elif isinstance(val, datetime):
|
|
407
|
+
temporal_count += 1
|
|
408
|
+
elif isinstance(val, str):
|
|
409
|
+
if _is_temporal_value(val):
|
|
410
|
+
temporal_count += 1
|
|
411
|
+
elif not is_db_string and _is_numeric_string(val):
|
|
412
|
+
numeric_count += 1
|
|
413
|
+
else:
|
|
414
|
+
categorical_count += 1
|
|
415
|
+
else:
|
|
416
|
+
categorical_count += 1
|
|
417
|
+
|
|
418
|
+
total = len(sample_values)
|
|
419
|
+
if total == 0:
|
|
420
|
+
return "categorical"
|
|
421
|
+
|
|
422
|
+
if numeric_count / total > 0.8:
|
|
423
|
+
return "numeric"
|
|
424
|
+
if temporal_count / total > 0.8:
|
|
425
|
+
return "temporal"
|
|
426
|
+
|
|
427
|
+
return "categorical"
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _is_temporal_value(value: Any) -> bool:
|
|
431
|
+
"""Check if a value appears to be temporal (date/time).
|
|
432
|
+
|
|
433
|
+
Recognises standard date formats and common period strings:
|
|
434
|
+
- ISO dates: 2024-01-15, 2024/01/15
|
|
435
|
+
- US dates: 01/15/2024, 01-15-2024
|
|
436
|
+
- Text dates: Jan 15, 2024
|
|
437
|
+
- Year-months: 2024-01, Jan 2024
|
|
438
|
+
- Quarters: 2024-Q1, Q1 2024, 2024Q1
|
|
439
|
+
- Plain years: 2024 (4-digit, in plausible range)
|
|
440
|
+
"""
|
|
441
|
+
if isinstance(value, datetime):
|
|
442
|
+
return True
|
|
443
|
+
if not isinstance(value, str):
|
|
444
|
+
return False
|
|
445
|
+
date_patterns = [
|
|
446
|
+
r"^\d{4}-\d{2}-\d{2}", # 2024-01-15
|
|
447
|
+
r"^\d{2}/\d{2}/\d{4}", # 01/15/2024
|
|
448
|
+
r"^\d{2}-\d{2}-\d{4}", # 01-15-2024
|
|
449
|
+
r"^\d{4}/\d{2}/\d{2}", # 2024/01/15
|
|
450
|
+
r"^\w{3}\s+\d{1,2},?\s+\d{4}", # Jan 15, 2024
|
|
451
|
+
r"^\d{4}-Q[1-4]$", # 2024-Q1
|
|
452
|
+
r"^Q[1-4]\s*\d{4}$", # Q1 2024, Q12024
|
|
453
|
+
r"^\d{4}Q[1-4]$", # 2024Q1
|
|
454
|
+
r"^\d{4}-\d{2}$", # 2024-01 (year-month)
|
|
455
|
+
r"^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{4}$", # Jan 2024
|
|
456
|
+
]
|
|
457
|
+
return any(re.match(pattern, value, re.IGNORECASE) for pattern in date_patterns)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _is_numeric_string(value: str) -> bool:
|
|
461
|
+
"""Check if a string value represents a number."""
|
|
462
|
+
try:
|
|
463
|
+
float(value.replace(",", "").replace("$", "").replace("%", ""))
|
|
464
|
+
return True
|
|
465
|
+
except (ValueError, AttributeError):
|
|
466
|
+
return False
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _analyze_cardinality(
|
|
470
|
+
data: list[dict[str, Any]],
|
|
471
|
+
columns: list[str],
|
|
472
|
+
) -> dict[str, int]:
|
|
473
|
+
"""Calculate cardinality (distinct value count) for each column."""
|
|
474
|
+
cardinalities: dict[str, int] = {}
|
|
475
|
+
for col in columns:
|
|
476
|
+
distinct_values = set()
|
|
477
|
+
for row in data:
|
|
478
|
+
val = row.get(col)
|
|
479
|
+
if val is not None:
|
|
480
|
+
distinct_values.add(str(val))
|
|
481
|
+
cardinalities[col] = len(distinct_values)
|
|
482
|
+
return cardinalities
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def is_auto_chart_type(chart_type: str | None) -> bool:
|
|
486
|
+
"""Check if chart type indicates auto-detection should be used."""
|
|
487
|
+
if chart_type is None:
|
|
488
|
+
return True
|
|
489
|
+
return chart_type.lower() == "auto"
|