Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,65 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [1.13.0] - 2026-10-06

### Added
- **Metrics next to the model.** A second, optional file beside the model,
`<stem>.metrics.yml`, says what the model's numbers mean, defined by its own spec, [metrics
spec 0.1.0](model2data/spec/metrics/README.md), with a JSON Schema at
`https://www.jbanalytica.com/model2data/spec/metrics/0.1.0/metrics.schema.json`. A metric is a
column aggregated (`measure: orders.total_amount`, by `agg` or the column's own `measure`), a
row count (`count: orders`), a ratio of two metrics' totals (`ratio: {numerator, denominator}`),
or arithmetic over metrics (`expression: revenue - refunds`). Simple metrics and row counts take
a `where` filter (a value, a list, or `eq`, `ne`, `in`, `not_in`, `gt`, `gte`, `lt`, `lte`,
`between`, `is_null`, combined with `any` and `all`, with nulls as in SQL) and a `time` column;
a filter may name a column of any table the metric's table reaches through its foreign keys,
many-to-one, along exactly one path. Every `measure` column of the model also yields a metric of
its own (`orders_total_amount`) and every fact a row count (`orders_count`), named as model2data
studio's semantic export names them; `infer: false` turns that off, and a metric of the same
name in the file replaces the inferred one. `format: percent` expects a fraction (0.6 shows as
60%) and is passed on as a percent format, never multiplied. `dimensions:` offers a column as a dimension, with a
label, or never. **The generator never reads the file**: a model generates the same data with
or without one.
- **`model2data validate` checks metrics files** (`*.metrics.yml`): the schema, then against the
model, `--model`, or else the model beside it with the same stem. It names every unknown table
or column, an aggregation that does not fit its column, an unknown metric in a ratio or
expression, a cycle between metrics, a filter column no path reaches or two paths reach (naming
both), a value that is not a member of its enum or not of its column's type, and a comparison
on an enum, boolean or text column; it warns about a metric with no date to report it by and a
filter that can never match the generated data (a `gt` above a column's `max`).
- **`generate --metrics FILE`** adds, and changes no other file:
`metric_values.json`, each metric's **known value** over the generated data, overall and per
calendar month (counts exact, everything else rounded to 6 decimal places, half to even; the
same seed gives the same bytes); a singular dbt test per metric,
`data-tests/metrics/metric_<name>.sql`, that computes it over the project's staging models and
fails unless it gets that value, so `dbt build` proves the metric logic on the warehouse, with
defects and `--days` too; and `osi/<project>.yml`, the model and its metrics as an Apache Ossie
(Open Semantic Interchange) 0.1.1 semantic model, as tagged `osi-0.1.1-rc1` in apache/ossie (no
final 0.1.1 tag exists yet; 0.2.0 is an unreleased draft). A
metrics file beside the model is not picked up on its own: an unchanged command keeps writing
the same files.
- **`model2data metrics list`** shows every metric, inferred ones included, and **`model2data
metrics export --to ossie`** writes the Ossie file without generating. What Ossie 0.1.1 has no
field for (a metric's label, format and time, an enum's members, a table's role and grain) goes
in `custom_extensions` and is listed at the top of the file.
- **`model2data.metrics`**, the Python API: `load` and `validate` a metrics file, `resolve` it with
its model into one semantic representation (entities, relationships, dimensions, measures and
metrics with their join paths), `known_values` over a run's frames, `to_ossie`, and
`write_metric_tests`.
- **`examples/ecommerce.metrics.yml`**: revenue, orders, average order value and units sold for
the e-commerce example, for `--metrics`.

### Changed
- The spec's example model, `model2data/spec/examples/coffee_webshop.model2data.yml`, marks
`orders.total_amount` and `order_items.quantity` as measures, for the metrics example beside
it. No model under `examples/` changed, and no output changed for any model.
- **The README and the PyPI page were rewritten** to show what model2data does today: what you
get, one capability per line with a link; a before and after of a real run with metrics; who
it is for; and how it works, with a new demo GIF. The reference that was in the README moved to
`docs/`, one page per topic (the model file, generating, the dbt project, days, history, time
shapes, distributions, defects, metrics, CI, agents), worded as it was.

## [1.12.0] - 2026-10-06

### Changed
Expand Down
3 changes: 2 additions & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ Releases are published to PyPI automatically when a GitHub Release is published
4. The floating major tag (`uses: JB-Analytica/model2data@v1`) must follow the release.
`.github/workflows/major-tag.yml` does it on `release: published` for `vX.Y.Z` tags. By
hand: `git tag -f v1 v1.10.0 && git push -f origin v1`. Also bump the default `version` input
in `action.yml`, and the versions in the README's CI snippets, to the new release.
in `action.yml`, and the versions in the CI snippets of
`docs/ci.md`, to the new release.
5. Confirm the new version is live: `pip index versions model2data`, or check
`https://pypi.org/project/model2data/<version>/` (the PyPI JSON API can lag the actual
upload by a minute or two — the version page itself is the more immediate check).
Expand Down
25 changes: 23 additions & 2 deletions LLMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ generated project), a `TableGroup` its `groups`.
for parsing and generate independent per-column FK values, but — same caveat as above — the
combination isn't guaranteed to match a real parent row unless separately enforced.
- Only DuckDB (default, zero-config) and Postgres (`--adapter postgres`, needs
`pip install "model2data[postgres]"` and connection env vars — see README.md) are supported
`pip install "model2data[postgres]"` and connection env vars — see
[docs/dbt-project.md](docs/dbt-project.md)) are supported
targets today.
- To show that a project's tests fire (or to teach dbt), generate with `--defects training`,
or list defects per table in a `.model2data.yml` (`defects: [{type: nulls, column: x, count:
Expand All @@ -245,6 +246,18 @@ generated project), a `TableGroup` its `groups`.
- For a source that keeps its own history (an SCD2 table to snapshot or join point-in-time),
set `history: true` in the table's `incremental` and generate with `--days N`:
`<table>_history` gets every version with `valid_from`, `valid_to` and `is_current`.
- When the user names the numbers that matter (revenue, orders, conversion), write them in a
metrics file beside the model, `<stem>.metrics.yml` (metrics spec 0.1.0,
`model2data/spec/metrics/README.md`): `measure: table.column` with `agg`, `count: table`,
`ratio: {numerator, denominator}` or `expression: a - b` over metric names, and `where` filters
(`table.column: value`, a list, or `{ne|in|not_in|gt|gte|lt|lte|between|is_null: ...}`, combined
with `any`/`all`). `format: percent` means the value is a fraction (0.6 shows as 60%): write
shares as `a / b`, never `* 100`. A filter column must be on the metric's table or reachable through foreign
keys child-to-parent along one path. Run `model2data validate <stem>.metrics.yml`, then generate
with `--metrics <stem>.metrics.yml`: `metric_values.json` holds each metric's known value, and
`dbt build` runs a test per metric that must pass. A failing metric test means the metric's SQL
and the data disagree; never edit `metric_values.json` to make it pass. The file must be passed;
`generate` does not pick it up from beside the model.
- model2data requires dbt-core >= 1.11 (tracking dbt's own supported-version policy). Everything
here, `--unit-tests` included, works with a plain `pip install model2data`.

Expand Down Expand Up @@ -280,7 +293,15 @@ model2data --file SCHEMA.dbml [OPTIONS]
tests are seen to fail; writes defects_report.json and
EXPECTED_FAILURES.md into the project. none: ignore every defect,
the model's own too
--metrics PATH The model's metrics file: adds metric_values.json (known values),
osi/NAME.yml (Apache Ossie 0.1.1) and data-tests/metrics/ (a dbt test
per metric). Changes no other file

model2data validate FILE... [--model MODEL] a metrics file (*.metrics.yml) is checked against
--model, or the model beside it with its stem
model2data metrics list -f MODEL [--metrics FILE] every metric, inferred too
model2data metrics export -f MODEL [--metrics FILE] --to ossie [-o FILE] without generating
```

For anything not covered here, see [README.md](README.md) — this file exists to make a schema
For anything not covered here, see [README.md](README.md) and [docs/](docs/README.md) — this file exists to make a schema
→ demo turnaround fast, not to duplicate the full documentation.
Loading
Loading