Metadata-Version: 2.4
Name: coop-dax-review
Version: 0.6.3
Summary: Offline, advisory DAX/model standards linter for Power BI semantic models: parses TMDL/.bim, checks measures and model structure against the team's DAX standards, and reports anything that doesn't match. Human report + machine JSON for the company agent. Never edits or blocks.
Project-URL: Homepage, https://github.com/kabukisensei/coop-dax-review
Project-URL: Repository, https://github.com/kabukisensei/coop-dax-review
Project-URL: Issues, https://github.com/kabukisensei/coop-dax-review/issues
Author: Aaron Jennings
License: MIT
License-File: LICENSE
Keywords: dax,fabric,linter,powerbi,semantic-model,standards,tmdl
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Requires-Dist: click>=8.1
Requires-Dist: coop-review-core>=0.1.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: questionary>=2.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Description-Content-Type: text/markdown

# coop-dax-review

Offline, **advisory** DAX/model standards linter for our Power BI semantic models. It parses
TMDL (and legacy `.bim`) models, builds a model catalog, checks measures **and model structure**
against `docs/standards.md` (our DAX standards + Microsoft/Tabular best practices), and surfaces
anything that doesn't match. **It never edits or blocks — it only reports.** Human reports (a
sectioned, colorized terminal report, Markdown, or a self-contained branded HTML file) and
**machine JSON for the company analytics agent**.

Sibling tool to [`coop-sql-review`](https://github.com/kabukisensei/coop-sql-review) — same
architecture and contracts.

## Install

```sh
pipx install coop-dax-review        # from PyPI
```

Use `pipx`, not system `pip`, so the tool stays isolated from other CLIs (`ms-fabric-cli`,
`azure-cli`) it might otherwise fight over shared pins. For local development:

```sh
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
```

## Usage

```sh
coop-dax-review check [MODEL_PATHS...] [--format text|json|markdown|html] [-o FILE]
                      [--open/--no-open] [--color/--no-color] [--log-file FILE]
                      [--baseline FILE] [--write-baseline FILE]
                      [--min-severity error|warning|info] [--strict]
coop-dax-review rules                 # list every rule (id, severity, tier, agent?)
coop-dax-review upgrade               # show the command to update (never self-applies; alias: update)
coop-dax-review --version
```

- `MODEL_PATHS` point at a PBIP/TMDL model folder (`*.SemanticModel/definition/...`), any folder of
  `.tmdl` files, or a legacy `.bim` file. Directories are searched recursively; defaults to `.`.
- **Run it with no paths in a terminal** and it offers a checkbox picker of the subfolders to check
  (all selected by default — press ENTER to scan everything).
- **Advisory**: exit code is always `0`. `--strict` is the opt-in CI gate — exit `2` when any
  finding remains at/above `--min-severity`.
- `--standards <path>` overrides the bundled standards (e.g. point it at a canonical company
  standards file). Its sha256 travels in the JSON so the agent knows which standards a report used.
- A `rules.yml` beside the standards file (or `--config`) can disable rules, override severities,
  and **tune thresholds** — all with no rebuild. For example, raise what counts as a "non-trivial"
  measure:
  ```yaml
  rules:
    DAX-VAR-RETURN:
      params: { min_functions: 5 }   # also: DAX-COMPLEX-NO-HEADER.min_vars,
                                      # DAX-DISPLAY-FOLDERS.min_measures, DAX-SIMPLE-FUNCTIONS.min_calculates
  ```

```sh
coop-dax-review check ./MyModel.SemanticModel
coop-dax-review check . --format json --strict --min-severity warning
coop-dax-review check . --format html              # writes a report file and opens it in your browser
coop-dax-review check . --format markdown -o report.md
```

The default `--format text` is a **sectioned terminal report**: a banner, one section per model with
`ERROR`/`WARN`/`INFO` severity badges, and a `SUMMARY` panel. It's colorized automatically when
you're at a terminal and falls back to plain text when piped or redirected (override with
`--color`/`--no-color`; `NO_COLOR` is respected).

`--format html` produces a self-contained, branded HTML report (inline CSS + embedded logo, no
network). It is always written to a file — `coop-dax-review-report.html` by default, or wherever
`-o` points — and the path is printed and opened in your browser (pass `--no-open` to skip the open,
e.g. in CI). `upgrade`/`update` print the exact command to run yourself (`pipx upgrade
coop-dax-review`, etc.) rather than self-applying, since a package manager can't replace the tool
while it is running.

## What it checks

Run `coop-dax-review rules` for the live list. Deterministic rules (reported as findings):

| Rule | § | Sev | Flags |
|---|---|---|---|
| `DAX-MEASURE-CATEGORY` | 1 | warning | measure not named `[Category: Name]` |
| `DAX-MEASURE-NOT-PREFIXED` | 1 | warning | `Table[X]` where `X` is a measure (measures take no prefix) |
| `DAX-COLUMN-PREFIXED` | 1 | warning | bare `[X]` where `X` is a column (columns need `Table[X]`) |
| `DAX-VAR-RETURN` | 2 | info | non-trivial measure with no `VAR`/`RETURN` structure |
| `DAX-NO-NESTED-CALCULATE` | 3 | warning | `CALCULATE` nested inside `CALCULATE` |
| `DAX-FILTER-TABLE-IN-CALCULATE` | 4 | warning | `FILTER(<table>, <col> = ...)` where a plain column filter suffices |
| `DAX-SNOWFLAKE` | 6 | info | a table with relationships chained through it (snowflake link) |
| `DAX-BIDI-RELATIONSHIP` | 7 | warning | a bidirectional cross-filter relationship |
| `DAX-MARKED-DATE-TABLE` | 8 | warning | time-intelligence used but no marked Date table |
| `DAX-MEASURE-IN-ITERATOR` | 9 | info | a measure referenced inside a row iterator (hidden context transition) |
| `DAX-COMPLEX-NO-HEADER` | 12 | info | a complex measure (≥3 VARs) without a `/* ... */` header |
| `DAX-DIRECTLAKE-NO-CALC-COL` | 13 | warning | a calculated column in a Direct Lake model |
| `DAX-USE-DIVIDE` | 14 | warning | the `/` operator where `DIVIDE()` should be used |
| `DAX-FORMAT-STRING` | 15 | warning | a measure with no explicit `formatString` |
| `DAX-NO-FLOAT-KEYS` | 16 | info | a relationship key column typed `double` |
| `DAX-HIDE-FK-COLUMNS` | 17 | info | a visible foreign-key (relationship) column |
| `DAX-KEY-SUMMARIZEBY-NONE` | 18 | info | a numeric key column that auto-aggregates (`summarizeBy` ≠ none) |
| `DAX-DISPLAY-FOLDERS` | 19 | info | a measure-heavy table with no display folders |

Agent-judgment rules — the tool detects the construct but emits to the JSON `agent_review` list
(never an auto-finding), because the call needs intent the linter can't infer:

| Rule | § | Judges |
|---|---|---|
| `DAX-KEEPFILTERS-NEEDED` | 5 | whether a CALCULATE boolean filter needs `KEEPFILTERS` |
| `DAX-STAR-SCHEMA` | 6 | whether a snowflake chain should be flattened to a star |
| `DAX-CONTEXT-TRANSITION` | 9 | whether an iterator's context transition is intended/correct |
| `DAX-SIMPLE-FUNCTIONS` | 10 | whether a CALCULATE-heavy measure could use simpler functions |
| `DAX-VALIDATION` | 11 | whether the §11 validation checklist was run for a non-trivial measure |
| `DAX-IMPLICIT-MEASURE` | 20 | whether a visible auto-aggregating numeric column should become an explicit measure |

See `RULES.md` for the full taxonomy. `docs/standards.md` §14–§20 are adopted Microsoft/Tabular
best practices (DIVIDE, format strings, key column types, hidden FKs, key summarizeBy, display
folders, explicit measures); `docs/standards-proposed-additions.md` is the original candidate list.

## Suppressing findings (adopting on an existing model)

Two deterministic, never-blocking ways to silence findings you've already triaged:

- **Inline** — drop a comment on a finding's line (or the line directly above it):
  ```
  // coop-dax-review:ignore DAX-VAR-RETURN reason: legacy measure, rewrite scheduled
  ```
  List several rule ids (`ignore DAX-A, DAX-B`), or use a bare `ignore` / `*` to silence every
  rule on that line. The `reason:` text is for humans; it's ignored by the parser.
- **Baseline (ratchet)** — record today's findings and surface only *new* ones going forward:
  ```sh
  coop-dax-review check . --write-baseline dax-baseline.json   # once, to capture the status quo
  coop-dax-review check . --baseline dax-baseline.json         # thereafter: only new findings appear
  ```
  Each finding has a stable, line-independent `fingerprint` (in the JSON), and the baseline is a
  sorted list of those. A baseline entry that no longer matches any finding (you fixed it) is
  reported as a diagnostic so the file self-cleans (`--write-baseline` to prune).

## Agent JSON contract

```json
{
  "tool": "coop-dax-review", "schema_version": 1, "version": "x.y.z",
  "standards": {"path": "...", "sha256": "..."},
  "models_checked": 2,
  "verdict": {"clean": false, "highest_severity": "warning"},
  "findings": [{"rule_id":"...","severity":"warning","model":"Sales","file":"...","line":12,
                "object":"[Sales: Revenue YTD]","message":"...","standard_ref":"§3","fingerprint":"4ad6aeb79867"}],
  "summary": {"error":0,"warning":2,"info":4},
  "agent_review": [{"rule_id":"...","model":"Sales","file":"...","line":40,"object":"[...]","note":"...","standard_ref":"§5","fingerprint":"..."}],
  "diagnostics": [{"severity":"warning","category":"parse_failed","file":"...","message":"..."}]
}
```

`schema_version` lets a consumer pin the shape; `verdict`/`models_checked` give a quick machine
verdict + coverage signal; each finding's `fingerprint` is a stable id for tracking across runs.

## Project docs

- `SPEC.md` — architecture, CLI, agent contract, milestones.
- `RULES.md` — every standard mapped to a concrete check (deterministic vs agent-judgment).
- `docs/standards.md` — the canonical DAX standards the linter checks against (bundled as package data).
- `CLAUDE.md` — orientation for Claude Code sessions in this repo.
