bsl-model-builder
Define and configure semantic tables and measures using the Boring Semantic Layer (BSL).
Install
mkdir -p .claude/skills/bsl-model-builder && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/8958" && unzip -o skill.zip -d .claude/skills/bsl-model-builder && rm skill.zipInstalls to .claude/skills/bsl-model-builder
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Build BSL semantic models with dimensions, measures, joins, and YAML config. Use for creating/modifying data models.Key capabilities
- →Transform Ibis tables into semantic models
- →Define dimensions and measures using lambda or class syntax
- →Perform one-to-many, one-to-one, and cross joins
- →Configure models via YAML files
- →Calculate time-based groupings using truncation
How it works
It maps raw data tables into semantic layers by defining categorical dimensions and quantitative measures, which can then be joined and queried.
Inputs & outputs
When to use bsl-model-builder
- →Define dimensions for grouping data
- →Create measures for quantitative analysis
- →Setup time-based grouping using truncation
About this skill
BSL Model Builder
You are an expert at building semantic models using the Boring Semantic Layer (BSL).
Core Concepts
A Semantic Table transforms a raw Ibis table into a reusable data model:
- Dimensions: Attributes to group by (categorical data)
- Measures: Aggregations and calculations (quantitative data)
Creating a Semantic Table
from boring_semantic_layer import to_semantic_table
# Start with an Ibis table
flights_st = to_semantic_table(flights_tbl, name="flights")
with_dimensions()
Define groupable attributes using lambda, unbound syntax (_.), or Dimension class:
from ibis import _
from boring_semantic_layer import Dimension
flights_st = flights_st.with_dimensions(
# Lambda - explicit
origin=lambda t: t.origin,
# Unbound syntax - concise
destination=_.dest,
year=_.year,
# Dimension class - with description (AI-friendly)
carrier=Dimension(
expr=lambda t: t.carrier,
description="Airline carrier code"
)
)
Time Dimensions
Use .truncate() for time-based groupings:
flights_st = flights_st.with_dimensions(
# Year, Quarter, Month, Week, Day
arr_year=lambda t: t.arr_time.truncate("Y"),
arr_month=lambda t: t.arr_time.truncate("M"),
arr_date=lambda t: t.arr_time.truncate("D"),
)
Truncate units: "Y" (year), "Q" (quarter), "M" (month), "W" (week), "D" (day), "h", "m", "s"
with_measures()
Define aggregations using lambda or Measure class:
from boring_semantic_layer import Measure
flights_st = flights_st.with_measures(
# Simple aggregations
flight_count=lambda t: t.count(),
total_distance=lambda t: t.distance.sum(),
avg_delay=lambda t: t.dep_delay.mean(),
max_delay=lambda t: t.dep_delay.max(),
# Composed measures (reference other measures)
avg_distance_per_flight=lambda t: t.total_distance / t.flight_count,
# Measure class - with description
avg_distance=Measure(
expr=lambda t: t.distance.mean(),
description="Average flight distance in miles"
)
)
Percent of Total with all(ref)
Pass a declared measure or reduction to t.all(...) to reference its value over
the entire dataset. There is no zero-argument t.all() form:
flights_st = flights_st.with_measures(
flight_count=lambda t: t.count(),
market_share=lambda t: t.flight_count / t.all(t.flight_count) * 100
)
Joins
join_many() - One-to-Many (LEFT JOIN)
# One carrier has many flights
carriers_with_flights = carriers_st.join_many(
flights_st,
lambda c, f: c.code == f.carrier
)
join_one() - At-Most-One Right Match (LEFT JOIN)
# Each flight matches at most one carrier
flights_with_carrier = flights_st.join_one(
carriers_st,
lambda f, c: f.carrier == c.code
)
join_cross() - Cartesian Product
all_combinations = flights_st.join_cross(carriers_st)
Join Predicates and Source-Aware Aggregation
join_one() and join_many() are left joins. Their on= argument can be a
column-name string, Deferred key, sequence of equality keys, or a lambda whose
predicate is a direct field equality (or conjunction of direct field
equalities). Source-aware aggregation rejects inequality, OR, cast, and
transformed join predicates because pre-aggregating those predicates from key
columns could change the matched rows. Aggregate each model first or restate the
relationship as equality keys. Use join_cross() for Cartesian products.
After joins: Fields are prefixed with table names (e.g., flights.origin, carriers.name)
Unique aliases are required: Every base model in a composed join tree must
have a distinct name. When the same physical table plays multiple roles, use
both .view() and explicit model aliases:
pickup_locs = to_semantic_table(locs_tbl.view(), "pickup_locs")
dropoff_locs = to_semantic_table(locs_tbl.view(), "dropoff_locs")
YAML Configuration
Define models in YAML for better organization:
# flights_model.yaml
profile: my_db # Optional: use a profile for connections
flights:
table: flights_tbl
dimensions:
origin: _.origin
destination: _.dest
carrier: _.carrier
arr_year: _.arr_time.truncate("Y")
measures:
flight_count: _.count()
total_distance: _.distance.sum()
avg_distance: _.distance.mean()
carriers:
table: carriers_tbl
dimensions:
code: _.code
name: _.name
measures:
carrier_count: _.count()
YAML uses unbound syntax only (_.field), not lambdas.
Loading YAML Models
from boring_semantic_layer import from_yaml
# With profile (recommended)
models = from_yaml("flights_model.yaml")
# With explicit tables
models = from_yaml(
"flights_model.yaml",
tables={"flights_tbl": flights_tbl, "carriers_tbl": carriers_tbl}
)
flights_sm = models["flights"]
Best Practices
- Add descriptions to dimensions/measures for AI-friendly models
- Use meaningful names that reflect business concepts
- Define composed measures to avoid repetition
- Use YAML for production models (version control, collaboration)
- Use profiles for database connections (see Profile docs)
- Choose join cardinality from the left side and give every joined source a unique name
Common Patterns
Derived Dimensions
flights_st = flights_st.with_dimensions(
# Extract from timestamp
arr_year=lambda t: t.arr_time.truncate("Y"),
arr_month=lambda t: t.arr_time.truncate("M"),
# Categorize numeric values (use ibis.cases - PLURAL, not ibis.case)
distance_bucket=lambda t: ibis.cases(
(t.distance < 500, "Short"),
(t.distance < 1500, "Medium"),
else_="Long"
)
)
Ratio Measures
flights_st = flights_st.with_measures(
total_flights=lambda t: t.count(),
delayed_flights=lambda t: (t.dep_delay > 0).sum(),
delay_rate=lambda t: t.delayed_flights / t.total_flights * 100
)
Additional Information
Available documentation:
- Getting Started: Introduction to BSL, installation, and basic usage with semantic tables
- Semantic Tables: Building semantic models with dimensions, measures, and expressions
- YAML Configuration: Defining semantic models in YAML files for better organization
- Profiles: Database connection profiles for connecting to data sources
- Composing Models: Joining multiple semantic tables together
- Query Methods: Complete API reference for group_by, aggregate, filter, order_by, limit, mutate
- Window Functions: Running totals, moving averages, rankings, lag/lead, and cumulative calculations
- Bucketing with Other: Create categorical buckets and consolidate long-tail into 'Other' category
- Nested Subtotals: Rollup calculations with subtotals at each grouping level
- Percent of Total: Calculate percentages using t.all() for market share and distribution analysis
- Dimensional Indexing: Compare values to baselines and calculate indexed metrics
- Charting Overview: Data visualization basics with automatic chart type detection
- Altair Charts: Interactive web charts with Vega-Lite via Altair backend
- Plotly Charts: Interactive charts with Plotly backend for dashboards
- Terminal Charts: ASCII charts for terminal/CLI with Plotext backend
- Sessionized Data: Working with session-based data and user journey analysis
- Comparison Queries: Period-over-period comparisons and trend analysis
When not to use it
- →When working with non-Ibis data structures
Limitations
- →YAML configuration does not support lambda syntax
- →Requires Ibis-compatible data sources
How it compares
It provides a structured semantic layer for Ibis tables, enabling reusable business logic that is independent of raw SQL or Ibis expressions.
Compared to similar skills
bsl-model-builder side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| bsl-model-builder (this skill) | 0 | 8mo | No flags | Intermediate |
| senior-data-engineer | 21 | 7mo | Review | Advanced |
| hugging-face-datasets | 1 | 6mo | Review | Intermediate |
| extract-test-set | 1 | 6mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
senior-data-engineer
davila7
World-class data engineering skill for building scalable data pipelines, ETL/ELT systems, and data infrastructure. Expertise in Python, SQL, Spark, Airflow, dbt, Kafka, and modern data stack. Includes data modeling, pipeline orchestration, data quality, and DataOps. Use when designing data architectures, building data pipelines, optimizing data workflows, or implementing data governance.
hugging-face-datasets
patchy631
Create and manage datasets on Hugging Face Hub. Supports initializing repos, defining configs/system prompts, streaming row updates, and SQL-based dataset querying/transformation. Designed to work alongside HF MCP server for comprehensive dataset workflows.
extract-test-set
tradingstrategy-ai
Extract raw price dataframe for a test case
data-quality-frameworks
Anhvu1107
ALWAYS use this when the request matches Data Quality Frameworks: Implement data quality validation with Great Expectations, dbt tests, and data contracts.
df-basic-stats
qmakescl
>
add-etf
michaellaret7
Add an ETF to the database with proper classification. Handles web research, DB format matching, confirmation, and execution.