Skip to content

Add curated semantic enrichment to the Jaffle Shop demo#250

Merged
whimo merged 5 commits into
mainfrom
artemy/demo-enrichment
Jul 21, 2026
Merged

Add curated semantic enrichment to the Jaffle Shop demo#250
whimo merged 5 commits into
mainfrom
artemy/demo-enrichment

Conversation

@whimo

@whimo whimo commented Jul 20, 2026

Copy link
Copy Markdown
Member

Summary

The stock Jaffle Shop demo ingested barebones models — bare columns, no labels, descriptions, or measures. This PR layers the same kind of curated enrichment the Storyline demo has onto SLayer's bundled demo, adapted for the dollars-native loader (SLayer converts jafgen's cents on load, so no dollars aggregation or / 100.0 is needed):

  • Column labels + descriptions for every column and model descriptions for all 7 models
  • Formats: currency on subtotal / tax_paid / order_total / price / cost, percent on stores.tax_rate — replacing only the bare auto-ingested INTEGER/FLOAT defaults (DEV-1538 convention), so user-tuned formats survive
  • 16 saved measures with labels, descriptions, and result type (feeds the Flight wire schema): total_revenue, net_sales, tax_collected, order_count, unique_customers, avg_order_value (with nullif guard), effective_tax_rate, sales_weighted_aov on orders, plus counts/averages on the other models
  • Custom-aggregation example: a weighted_avg built-in override on orders defaulting the weight to subtotal, so bare order_total:weighted_avg yields a sales-weighted AOV

apply_demo_enrichment is additive-only and idempotent — labels/descriptions fill only where unset, measures/aggregations merge by name — and runs on both paths of ensure_demo_datasource: fresh ingest and the reuse fast path (saving only when something changed), so pre-enrichment demo installs upgrade on their next --demo startup.

The checked-in lightning-talk example store (docs/examples/09_lightning_talk/slayer_models/) is regenerated with the enrichment — a one-time migration, since its notebook calls ensure_demo_datasource; re-runs are no-ops.

Testing

  • All 16 measures execute end-to-end on DuckDB, including the weighted-average math and currency-format inheritance into response attributes
  • 6 new unit tests in tests/test_demo_cli.py covering idempotency, user-edit preservation, format-override rules, duplicate-measure merging, and fast-path persistence (22 pass)
  • Lightning-talk notebook integration test passes against the regenerated store
  • ruff check clean

Summary by CodeRabbit

  • New Features

    • Enhanced the Jaffle Shop demo datasource/model auto-ingestion with curated semantic metadata (labels/descriptions), default time dimension, currency/percent formatting, and ready-made measures plus a weighted_avg aggregation.
    • Enrichment is idempotent and additive-only, preserves existing user edits, and prevents duplicate measures (including on reuse/fast-path).
  • Documentation

    • Updated CLI, getting-started, and demo command/reference docs to explain the optional --ingest behavior and enrichment details.
  • Tests

    • Added end-to-end coverage for enrichment completeness, idempotency, customization preservation, persistence, and skip behavior; updated Metabase E2E expectations accordingly.

Auto-ingested demo models were barebones: bare columns, no labels,
descriptions, or measures. Mirror the Storyline demo's enrichment,
adapted for SLayer's dollars-native loader:

- Column labels/descriptions and model descriptions for all 7 models
- Currency/percent formats (replacing only the bare auto-ingested
  INTEGER/FLOAT defaults)
- 16 saved measures with labels, descriptions, and result types
  (total_revenue, avg_order_value, effective_tax_rate,
  unique_customers, sales_weighted_aov, ...)
- A weighted_avg built-in override on orders defaulting the weight
  to subtotal, as a custom-aggregation example

apply_demo_enrichment is additive-only and idempotent (fills only
unset fields, merges measures/aggregations by name) and runs on both
paths of ensure_demo_datasource — fresh ingest and the reuse fast
path (saving only on change), so existing demo installs upgrade on
their next --demo startup.

The checked-in lightning-talk example store is regenerated with the
enrichment (one-time migration; its notebook calls
ensure_demo_datasource).
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4a117e84-f789-4801-bb73-fdb1db53c9a0

📥 Commits

Reviewing files that changed from the base of the PR and between e69a3ed and 152aa06.

📒 Files selected for processing (1)
  • tests/integration/test_metabase_e2e.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/integration/test_metabase_e2e.py

📝 Walkthrough

Walkthrough

Changes

The Jaffle Shop demo now applies curated, additive semantic enrichment during model ingestion and datasource reuse. Example YAML models and CLI documentation describe the resulting metadata, measures, formats, aggregation, and idempotent behavior. Tests cover enrichment and persistence.

Jaffle Shop demo enrichment

Layer / File(s) Summary
Enrichment definition and application
slayer/demo/jaffle_shop.py
Adds curated per-model metadata, formats, measures, aggregations, and additive enrichment logic.
Datasource wiring and validation
slayer/demo/jaffle_shop.py, tests/test_demo_cli.py, tests/integration/test_metabase_e2e.py
Applies enrichment during ingestion and reuse, with tests for idempotency, preservation, deduplication, format handling, persistence, and enriched column counts.
Example semantic models
docs/examples/09_lightning_talk/slayer_models/models/jaffle_shop/*
Adds descriptions, labels, formats, measures, and aggregation examples to Jaffle Shop models.
CLI documentation
CLAUDE.md, docs/getting-started/cli.md, docs/reference/cli.md
Documents --ingest, enrichment behavior, rerun semantics, sizing, and jafgen invocation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant ensure_demo_datasource
  participant apply_demo_enrichment
  participant ModelStorage
  CLI->>ensure_demo_datasource: create demo with optional ingest
  ensure_demo_datasource->>apply_demo_enrichment: enrich ingested or reused models
  apply_demo_enrichment-->>ensure_demo_datasource: return updated model
  ensure_demo_datasource->>ModelStorage: save changed model
  ModelStorage-->>CLI: return demo models
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding curated semantic enrichment to the Jaffle Shop demo.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch artemy/demo-enrichment

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
slayer/demo/jaffle_shop.py (2)

462-467: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate default_time_dimension logic between the caller and apply_demo_enrichment.

ensure_demo_datasource's ingestion loop (lines 858-860) unconditionally sets model.default_time_dimension from DEFAULT_TIME_DIMENSIONS, and apply_demo_enrichment (lines 462-467) now does the same thing again, guarded by an is None check. Since apply_demo_enrichment is called right after on the same model, the caller's block is now redundant and the two mechanisms could drift out of sync if one is edited without the other.

♻️ Proposed cleanup
     for model in models:
-        if model.name in DEFAULT_TIME_DIMENSIONS:
-            model.default_time_dimension = DEFAULT_TIME_DIMENSIONS[model.name]
         apply_demo_enrichment(model)

Also applies to: 858-861

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@slayer/demo/jaffle_shop.py` around lines 462 - 467, Remove the redundant
default_time_dimension assignment from ensure_demo_datasource’s ingestion loop
and keep this responsibility in apply_demo_enrichment. Preserve the existing
guarded assignment using DEFAULT_TIME_DIMENSIONS in apply_demo_enrichment so
each model is enriched consistently in one place.

480-498: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Measures/aggregations/formats are appended by reference, not copied — shared mutable state across model instances.

DEMO_ENRICHMENT is built once at module import (line 428) and reused for every call. new_measures, new_aggs, and column.format = col_spec.format all attach the same ModelMeasure/Aggregation/NumberFormat Pydantic instances (mutable by default) onto every enriched model. If any code elsewhere ever mutates one of these objects in place (e.g. hoisted-transform naming, per the coding guidelines on hoisted transform aliasing), that mutation would leak across every other model that received the same spec object. Copying defensively avoids this class of bug.

🛡️ Proposed fix
-            column.format = col_spec.format
+            column.format = col_spec.format.model_copy()
             changed = True
     ...
-    new_measures = [m for m in spec.measures if m.name not in existing_measures]
+    new_measures = [m.model_copy() for m in spec.measures if m.name not in existing_measures]
     ...
-    new_aggs = [a for a in spec.aggregations if a.name not in existing_aggs]
+    new_aggs = [a.model_copy() for a in spec.aggregations if a.name not in existing_aggs]

Could you confirm (or point to) whether anything downstream ever mutates ModelMeasure/Aggregation/NumberFormat instances in place after they're attached to a SlayerModel (e.g. hoisted-transform alias assignment)? If so this becomes a real cross-model contamination bug rather than a defensive-only concern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@slayer/demo/jaffle_shop.py` around lines 480 - 498, In the enrichment flow,
defensively deep-copy mutable spec objects before attaching them to a
SlayerModel. Update new_measures and new_aggs to contain independent copies, and
copy col_spec.format before assigning it to column.format in the format-update
block. Preserve the existing deduplication and changed-tracking behavior while
ensuring no ModelMeasure, Aggregation, or NumberFormat instance is shared across
enriched models.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@slayer/demo/jaffle_shop.py`:
- Around line 442-500: Reduce cognitive complexity in apply_demo_enrichment by
extracting the column-enrichment loop into a helper and consolidating the named
measures and aggregations merge logic into a reusable helper such as
_merge_named. Preserve the current additive, idempotent behavior and ensure
apply_demo_enrichment still tracks whether any helper changed the model before
returning.
- Around line 442-454: Update apply_demo_enrichment to return False before
looking up or applying DEMO_ENRICHMENT when the model is SQL- or query-backed,
including sql_table, sql, or source_queries configurations. Continue enriching
only table-backed models while preserving the existing idempotent behavior for
eligible models.

---

Nitpick comments:
In `@slayer/demo/jaffle_shop.py`:
- Around line 462-467: Remove the redundant default_time_dimension assignment
from ensure_demo_datasource’s ingestion loop and keep this responsibility in
apply_demo_enrichment. Preserve the existing guarded assignment using
DEFAULT_TIME_DIMENSIONS in apply_demo_enrichment so each model is enriched
consistently in one place.
- Around line 480-498: In the enrichment flow, defensively deep-copy mutable
spec objects before attaching them to a SlayerModel. Update new_measures and
new_aggs to contain independent copies, and copy col_spec.format before
assigning it to column.format in the format-update block. Preserve the existing
deduplication and changed-tracking behavior while ensuring no ModelMeasure,
Aggregation, or NumberFormat instance is shared across enriched models.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4ea98120-6663-4158-bf1c-8fad9b38dfc5

📥 Commits

Reviewing files that changed from the base of the PR and between dc2ecad and 2e4fdd5.

📒 Files selected for processing (12)
  • CLAUDE.md
  • docs/examples/09_lightning_talk/slayer_models/models/jaffle_shop/customers.yaml
  • docs/examples/09_lightning_talk/slayer_models/models/jaffle_shop/items.yaml
  • docs/examples/09_lightning_talk/slayer_models/models/jaffle_shop/orders.yaml
  • docs/examples/09_lightning_talk/slayer_models/models/jaffle_shop/products.yaml
  • docs/examples/09_lightning_talk/slayer_models/models/jaffle_shop/stores.yaml
  • docs/examples/09_lightning_talk/slayer_models/models/jaffle_shop/supplies.yaml
  • docs/examples/09_lightning_talk/slayer_models/models/jaffle_shop/tweets.yaml
  • docs/getting-started/cli.md
  • docs/reference/cli.md
  • slayer/demo/jaffle_shop.py
  • tests/test_demo_cli.py

Comment thread slayer/demo/jaffle_shop.py
Comment thread slayer/demo/jaffle_shop.py
whimo added 2 commits July 21, 2026 12:57
…jects

- apply_demo_enrichment skips models not backed by sql_table (DEV-1356
  convention: the curated spec assumes the physical jaffle tables)
- Extract _enrich_column / _new_named_entries helpers (SonarCloud
  cognitive-complexity gate)
- Deep-copy measures/aggregations/formats when attaching so the
  module-level DEMO_ENRICHMENT spec can't leak mutations across models
- Drop the duplicate default_time_dimension assignment in
  ensure_demo_datasource (now solely owned by apply_demo_enrichment)
The MBQL source-table browse of orders now returns the 7 physical
columns plus the saved measures the demo enrichment adds (the facade
catalog exposes measures as queryable fields). Derive the expected
count from DEMO_ENRICHMENT instead of hardcoding.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/integration/test_metabase_e2e.py`:
- Line 461: Move the DEMO_ENRICHMENT import out of the test function and place
it with the module-level imports at the top of
tests/integration/test_metabase_e2e.py, leaving the test’s usage unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 33b7cd71-fc80-4a3e-9806-95c62d3cd75b

📥 Commits

Reviewing files that changed from the base of the PR and between e070bd2 and e69a3ed.

📒 Files selected for processing (1)
  • tests/integration/test_metabase_e2e.py

Comment thread tests/integration/test_metabase_e2e.py Outdated
@sonarqubecloud

Copy link
Copy Markdown

@whimo
whimo merged commit cdeadff into main Jul 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant