Skip to content

Compose two model variants

Reuse a source foundation without deploying it as a physical base model. Give each output its own additions, Report and validations.

Before you start

Continue the native Parcel project, keeping its working model, Report and overlays. Add a copy named ParcelBase as the source foundation for two audience variants. Nothing renames or removes Parcel.

From the project root, make the local source copies below. Existing destinations cause copytree to refuse rather than overwrite them:

prepare-variants.py
import json
import shutil
from pathlib import Path

root = Path("PowerBI/Parcel")
shutil.copytree(root / "Parcel.SemanticModel", root / "ParcelBase.SemanticModel")
for name in ("ParcelOperations", "ParcelExecutive"):
    report = root / f"{name}.Report"
    shutil.copytree(root / "Parcel.Report", report)
    path = report / "definition.pbir"
    definition = json.loads(path.read_text())
    definition["datasetReference"] = {"byPath": {"path": f"../{name}.SemanticModel"}}
    path.write_text(json.dumps(definition, indent=2) + "\n")

Run python prepare-variants.py once. ParcelBase retains Delivery, Depot, their inline partitions and relationship. Add PowerBI/Parcel/ParcelBase.tmdl to supply the internal-cost table used by this example:

model Model
    annotation 'Weaver.AutoHideForeignKeys' = true

table InternalCosts
    column Amount
        dataType: int64
        sourceColumn: Amount
    partition InternalCosts = m
        mode: import
        source = #table(type table [Amount = Int64.Type], {{7}})

The output TMDL files below compose that local foundation. They are not precompiled, independently populated Desktop models. Their Reports use ordinary native Report definitions and Build resolves each same-name model association.

1. Read each difference

PowerBI/Parcel/ParcelOperations.tmdl:

model Model
    annotation 'Weaver.BaseSemanticModels' = ParcelBase

table Delivery
    measure 'Delivery Rate' = DIVIDE([Delivery Count], COUNTROWS(Delivery))
        formatString: 0.0%

Operations adds Delivery Rate and keeps InternalCosts.

PowerBI/Parcel/ParcelExecutive.tmdl:

model Model
    annotation 'Weaver.BaseSemanticModels' = ParcelBase

table InternalCosts
    annotation 'Weaver.Exclude' = true

table Delivery
    measure 'Delivery Count'
        formatString: 0 "delivered"

Executive excludes the InternalCosts table and gives Delivery Count its own format. Exclusion is not RLS/OLS or an access-control policy; validate security separately.

Each output owns its validations. Create the following file under both PowerBI/Parcel/assumptions/ParcelOperations/ and PowerBI/Parcel/assumptions/ParcelExecutive/, named Parcel.PositiveDeliveryCount.dax:

/*
Assumption ID: Parcel.PositiveDeliveryCount
Description: The delivery count is positive.
*/
EVALUATE FILTER(ROW("DeliveryCount", [Delivery Count]), [DeliveryCount] <= 0)

Composition does not copy a base’s validations. Under targets in the existing workspace-config.yml, add:

Add under targets
  SemanticModel/ParcelOperations: ParcelOperations
  Report/ParcelOperations: ParcelOperations
  SemanticModel/ParcelExecutive: ParcelExecutive
  Report/ParcelExecutive: ParcelExecutive

Keep the existing Parcel and source bindings. No ParcelBase target is needed because this task deploys only the two outputs.

2. Select only the outputs

From the same project root:

weaver check
weaver build --item SemanticModel/ParcelOperations --item Report/ParcelOperations --item SemanticModel/ParcelExecutive --item Report/ParcelExecutive

The command returned:

Terminal output exit 0
Workspace  Parcel Development
Catalogue  none
Targets
  SemanticModel/ParcelOperations
  Report/ParcelOperations
  SemanticModel/ParcelExecutive
  Report/ParcelExecutive


Build

    Create SemanticModel/ParcelExecutive                                                                  20.7s
    Create SemanticModel/ParcelOperations                                                                 20.6s
    Create Report/ParcelExecutive                                                                         20.6s
    Create Report/ParcelOperations                                                                        20.6s
  Create Power BI items                                                                                   1m22s
  Resolve semantic data sources                                                                            0.0s
    Resolve SemanticModel/ParcelOperations                                                                 0.0s
    Resolve Report/ParcelOperations                                                                        0.0s
    Resolve SemanticModel/ParcelExecutive                                                                  0.0s
    Resolve Report/ParcelExecutive                                                                         0.0s
  Read target inventories                                                                                  0.3s
  Read semantic sources                                                                                    0.0s
  Prepare bundle                                                                                           0.0s
Install selection
  SemanticModel/ParcelOperations
    changed                 1
    selected for build      1
  Report/ParcelOperations
    changed                 1
    selected for build      1
  SemanticModel/ParcelExecutive
    changed                 1
    selected for build      1
  Report/ParcelExecutive
    changed                 1
    selected for build      1

→   SemanticModel/ParcelExecutive · Deploy semantic definitions · 1 action
→   SemanticModel/ParcelOperations · Deploy semantic definitions · 1 action
✓   SemanticModel/ParcelOperations · Deploy semantic definitions · 1 action                               21.0s
→   SemanticModel/ParcelOperations, Report/ParcelExecutive · Verify deployed semantic definitions · 1 action
✓   SemanticModel/ParcelExecutive · Deploy semantic definitions · 1 action                                21.1s
→   SemanticModel/ParcelExecutive · Verify deployed semantic definitions · 1 action
✓   SemanticModel/ParcelOperations, Report/ParcelExecutive · Verify deployed semantic definitions · 1 action   20.5s
→   Report/ParcelOperations · Deploy Report definitions · 1 action
✓   SemanticModel/ParcelExecutive · Verify deployed semantic definitions · 1 action                       20.4s
→   Report/ParcelExecutive · Deploy Report definitions · 1 action
✓   Report/ParcelOperations · Deploy Report definitions · 1 action                                        20.3s
→   Report/ParcelOperations · Verify deployed Report definitions · 1 action
✓   Report/ParcelExecutive · Deploy Report definitions · 1 action                                         20.3s
→   Report/ParcelExecutive · Verify deployed Report definitions · 1 action
✓   Report/ParcelExecutive · Verify deployed Report definitions · 1 action                                20.8s
✓   Report/ParcelOperations · Verify deployed Report definitions · 1 action                               20.8s
  Install                                                                                                 1m22s
✓ Build                                                                                                   2m45s

Installation
    8 succeeded
    0 failed
    0 skipped
  Report  [local-path]
  Bundle  b0841a33f1dc2109ba3070f63cbb77ad57afc5341b2a86fc50cfa98f3ab31ad9
weaver load SemanticModel/ParcelOperations SemanticModel/ParcelExecutive
weaver test SemanticModel/ParcelOperations SemanticModel/ParcelExecutive --source .

The command returned:

Terminal output exit 0
Test

  Read validations                                                                                         0.1s
  Execute                                                                                                  1.8s
✓ Test                                                                                                     1.9s

test passed

Test summary
    2 passed
    0 failed

from pathlib import Path

import weaver

project = Path("builtin") / "project"
load_result = weaver.load(
    items=['SemanticModel/ParcelOperations', 'SemanticModel/ParcelExecutive'],
    source=project,
    workspace='Parcel Development',
)
load_result.report()
from pathlib import Path

import weaver

project = Path("builtin") / "project"
test_result = weaver.test(
    items=['SemanticModel/ParcelOperations', 'SemanticModel/ParcelExecutive'],
    source=project,
    workspace='Parcel Development',
)
test_result.report()

The command returned:

Notebook output
{'status': 'passed',
 'nodes': [{'logical_id': 'SemanticModel/ParcelExecutive/Parcel.PositiveDeliveryCount',
            'kind': 'assumption',
            'physical_target': 'SemanticModel/ParcelExecutive',
            'primitive_kind': 'semantic_validation',
            'dispatch_location': 'PowerBI/Parcel/assumptions/ParcelExecutive/Parcel.PositiveDeliveryCount.dax',
            'status': 'passed',
            'executed': True,
            'messages': [],
            'started_at': '2026-10-17T07:51:22.734510+00:00',
            'finished_at': '2026-10-17T07:51:23.356605+00:00',
            'violation_count': 0,
            'error_message': None},
           {'logical_id': 'SemanticModel/ParcelOperations/Parcel.PositiveDeliveryCount',
            'kind': 'assumption',
            'physical_target': 'SemanticModel/ParcelOperations',
            'primitive_kind': 'semantic_validation',
            'dispatch_location': 'PowerBI/Parcel/assumptions/ParcelOperations/Parcel.PositiveDeliveryCount.dax',
            'status': 'passed',
            'executed': True,
            'messages': [],
            'started_at': '2026-10-17T07:51:22.734510+00:00',
            'finished_at': '2026-10-17T07:51:23.695332+00:00',
            'violation_count': 0,
            'error_message': None}],
 'totals': {'planned': 2,
            'executed': 2,
            'passed': 2,
            'failed': 0,
            'invalid': 0,
            'missing_count': 0,
            'unexpected_count': 0,
            'violation_count': 0},
 'workflow_id': None,
 'started_at': '2026-10-17T07:51:22.734510+00:00',
 'finished_at': '2026-10-17T07:51:23.695773+00:00'}

Do not replace that Build selection with PowerBI/Parcel: the broad project selector includes the base. This configuration deliberately has no base target, so that broader request refuses. Source composition does not itself select a base for deployment.

3. Inspect the outputs

Both models and Reports now exist, and each Report is bound to its own model. Both models return Delivery Count = 2. The deployed definitions differ as declared: ParcelOperations has Delivery Rate and the InternalCosts table; ParcelExecutive has no InternalCosts table and formats Delivery Count as 0 "delivered". Shared policy from the previous task applies to both. No ParcelBase item was deployed.

For a second environment, create its configuration using workspace mappings, repeat the explicit four-item Build selection with that configuration, then refresh the two models. Do not edit the base merely to change destination names.

4. Diagnose a missing base

Save the Executive overlay, change ParcelBase to MissingBase in its annotation, and Check:

cp PowerBI/Parcel/ParcelExecutive.tmdl ParcelExecutive.tmdl.saved
weaver check

The command returned:

Terminal output exit 1
error: SemanticModel/ParcelExecutive references missing base 'MissingBase' in PowerBI/Parcel; bases must be local to this project

Enter to retry, Esc to exit.

Press Esc at the retry prompt, then restore the file and Check again:

mv ParcelExecutive.tmdl.saved PowerBI/Parcel/ParcelExecutive.tmdl
weaver check

The failing overlay names MissingBase. The first Check refuses it in the local project scope; restoring the file makes discovery valid again. Bases must be local to the same Power BI group. Composition recursively merges raw local bases before final policy and overlays, then applies handlers once to the final model. Cycles and repeated ancestors are refused, not silently deduplicated. A base contributes source, not another selected deployment or copied validations.