Dataface Tasks

Create canonical chart profile mapping layer before Vega-Lite emission

IDDFT_CORE-CREATE_CANONICAL_CHART_PROFILE_MAPPING_LAYER_BEFORE_VEGA_LITE_EMISSION
Statuscompleted
Priorityp1
Milestonem1-ft-analytics-analyst-pilot
Ownersr-engineer-architect
Completed bydave
Completed2026-03-27

Problem

Establish a dedicated mapping/profile boundary between the compiled Dataface chart contract and emitted Vega-Lite specs so renames, wraps, hidden fields, and logic-heavy divergences stop living ad hoc inside standard_renderer.

Context

  • Background research:
  • ai_notes/refactors/DATAFACE_SPEC_PROFILE_AND_VEGA_LITE_STRATEGY.md
  • ai_notes/research/DATAFACE_AS_A_DASHBOARD_SPEC_FROM_VEGA_LITE_OUTWARD.md
  • ai_notes/refactors/CHART_DEFAULTS_AND_PRECEDENCE_AUDIT.md
  • ai_notes/refactors/COMPILED_PRESENTATION_DEFAULTS_ARCHITECTURE.md
  • The long-term target is now clearer:
  • Dataface should have a fully defined Pydantic language layer
  • charts should start from a full Vega-Lite mirror surface
  • Dataface should then layer its own profile over that surface:
    • renames
    • wraps/shorthand
    • hidden or discouraged fields
    • Dataface-owned defaults/divergences
    • logic-heavy transforms where meaning is not a simple path rename
  • That means the architecture needs a central boundary between:
  • the compiled canonical Dataface chart contract
  • the final emitted Vega-Lite spec
  • Today that responsibility is still scattered across render-time code, especially:
  • dataface/core/render/chart/pipeline.py
  • dataface/core/render/chart/presentation.py
  • dataface/core/render/chart/standard_renderer.py
  • spec-building helpers that mix semantic shaping, default application, and Vega-Lite assembly
  • This creates several long-term problems:
  • renames such as horizontal_axis vs Vega-Lite-native axis paths have no single mapping home
  • one-to-many or conditional transforms risk being hidden in renderer helpers
  • compile/runtime boundaries stay blurry
  • the final emitter is not mechanical because it also owns Dataface profile logic
  • Desired end state:
  • compile produces a stable canonical CompiledChart
  • a dedicated profile-mapping layer converts canonical Dataface chart concepts into Vega-Lite-native concepts
  • standard_renderer becomes a much thinner emitter over already-mapped chart state plus compiled defaults
  • Constraint:
  • do not push data-dependent semantic enrichment into the emitter
  • keep variable/query/layout/dashboard concepts outside the Vega-Lite chart grammar
  • keep public state/input concepts unified under Dataface variables, not Vega-Lite params

Possible Solutions

  1. Keep evolving standard_renderer as the de facto mapping layer. Fastest short term, but it guarantees more hidden divergence logic and makes future renames/profile changes difficult to audit.
  2. Recommended: introduce an explicit chart profile-mapping layer between compiled Dataface charts and Vega-Lite emission. This creates one home for: - direct renames - path remaps - one-to-many expansions - conditional transforms - explicit disallow/hide decisions The renderer can then focus on mechanical emission.
  3. Skip the canonical mapping layer and map authored Dataface fields directly to Vega-Lite-native fields. Simpler initially, but it makes future divergence painful because the public API becomes tightly coupled to upstream naming.
  4. Build the full generated Vega-Lite Pydantic layer first and postpone mapping-boundary cleanup. Useful eventually, but it risks cementing today's mixed responsibilities before the architectural seam exists.

Plan

Selected: Option 2 — add a dedicated chart profile-mapping layer and slim standard_renderer into an emitter over compiled canonical chart state.

Files to modify: 1. dataface/core/render/chart/standard_renderer.py 2. dataface/core/render/chart/pipeline.py 3. dataface/core/render/chart/presentation.py 4. dataface/core/render/chart/spec_builders.py or a new dedicated profile module 5. dataface/core/compile/compiled_types.py or related canonical chart types as needed 6. tests covering compiled-chart -> profile-mapped-chart -> emitted Vega-Lite behavior

Implementation steps: 1. Inventory current Dataface-to-Vega-Lite mapping logic already embedded in renderer helpers. 2. Classify each case as: - direct rename - structural remap - one-to-many expansion - conditional transform - default ownership issue that belongs earlier in compile/default layering 3. Introduce a dedicated profile-mapping module and explicit transform helpers. 4. Define the canonical internal contract the mapping layer consumes from CompiledChart. 5. Move Dataface profile logic out of standard_renderer into the new mapping layer. 6. Reduce standard_renderer to: - consume mapped chart state - apply already-compiled defaults - emit Vega-Lite mechanically 7. Add regression tests that assert: - authored Dataface field -> canonical compiled field - canonical compiled field -> mapped Vega-Lite-native shape - mapped shape -> final emitted spec 8. Document the rule that future Dataface/Vega-Lite divergences must be implemented in the profile-mapping layer, not ad hoc in renderer code.

Implementation Progress

  • Created from architecture follow-up after the Dataface spec/backbone discussion.
  • Current agreed stance for this task:
  • treat the compiled canonical form as the stable internal contract
  • make Dataface/Vega-Lite divergence explicit
  • keep standard_renderer focused on emission rather than profile logic
  • [x] Inventoried Dataface→VL mapping logic in standard_renderer.py
  • [x] Created dataface/core/render/chart/profile.py as the single home for all Dataface/VL divergence:
  • CHART_TYPE_MAP — type alias renames (scatter→point, pie→arc, etc.)
  • MappedChart — frozen dataclass holding VL-native mark, encoding, and layout hints
  • map_to_vega_lite() — public API: ResolvedChart → MappedChart
  • Channel mapping: _map_x_encoding, _map_y_encoding, _map_other_encoding
  • Structural transforms: _apply_horizontal_bar_orientation, _apply_chart_sort, _apply_bar_axis_defaults
  • Layout measurement: _measure_categorical_label_width
  • [x] Refactored standard_renderer.py to consume MappedChart for the single-metric path — no more inline profile logic
  • [x] Updated build_layered_series_spec to use profile channel mappers instead of the old build_channel_encoding
  • [x] Added 18 unit tests in tests/core/test_chart_profile.py covering type mapping, channel encoding, orientation swap, sort, label width, format/zero propagation, and end-to-end regression
  • [x] Updated DESIGN.md file layout table to document profile.py
  • [x] All 2882 core tests pass (0 failures, 31 skipped, 3 xfailed)

QA Exploration

  • [x] N/A — pure internal refactor, no UI changes

Review Feedback

  • [ ] Review cleared