【Fabric】Semantic-Link-Labsでメジャーの説明を一括更新

## はじめに
Microsoft Fabric Advent Calendar 2024の14日目の記事です。

https://qiita.com/advent-calendar/2024/msfabric

 Power BIでライブ接続ではメジャーのDAXが確認できなくなるので、Tabular Editorを使って説明欄にDAXを転記することで、ライブ接続でもレポート作成者がDAXを確認できるようにする手順を書いてみました。
 
https://qiita.com/fumiki-k/private/1da60468dcb7db17c9f5

FabricのSemnatic-Link-Labsを使えば、複数のセマンティックモデルに対して一括更新ができるんじゃんと思い、試したことを記事にしました。

:::note warn
ちなみにPythonは初心者（かつ習熟する気もない💦）なので生成AIと対話しながらのコードです。もしかすると非常識な書き方になっているかもしれません。
:::


## Semantic-Link-Labsとは？
ReadMeの翻訳です。


> Semantic Link Labs は、Microsoft Fabric ノートブックで使用するために設計された Python ライブラリです。このライブラリは、Semantic Link の機能を拡張し、シームレスに統合して連携するための追加機能を提供します。Semantic Link Labs の目標は、技術的なプロセスを簡略化し、人々がより高いレベルの活動に集中できるようにし、人間の介入なしにマシンにより適したタスクを効率的に処理できるようにすることです。

Semantic Linkの拡張版ですが、Semantic Link自体がデータサイエンティストとBIの橋渡し的な言い方をしていてリリース当初はあまり関心が湧いていませんでした。よく調べていくと運用面での自動化が出来る機能満載ですね！

https://github.com/microsoft/semantic-link-labs#read-the-documentation-on-readthedocs

公式ドキュメントはこちら

https://semantic-link-labs.readthedocs.io/en/stable/index.html

## やりたいこと
- ワークスペース内の複数セマンティックモデル内のメジャーを一括更新したい
- セマンティックモデルは対象を絞る、もしくはすべてを対象とするかを選択したい

### 全体フロー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/2aca3038-13a3-ca00-bd17-3024abbe76db.png)

２つのノートブックを作成してData pipelineで更新するフローを作成しました
1. ワークスペースを指定してセマンティックモデルIDとセマンティックモデル名を取得
1. 対象のセマンティックモデル分だけループしてメジャー定義を更新

### ノートブック
#### ワークスペースとモデル名の一部を指定してセマンティックモデルを取得する
:::note info
通常のPythonを使用するとPipeline実行時にノートブックから値が戻らなかった為、言語はPySpark(Python)を使用しました。
まだPreviewで制限事項にも以下の記載があるので、もう少し待つしかないかなと。
:::

> Currently, the **data pipelines integration** , scheduler and Copilot on Python notebook **is not fully supported**  in several regions. The deployment process is still ongoing, please stay tuned as we continue to roll out support in more regions.

https://learn.microsoft.com/ja-jp/fabric/data-engineering/using-python-experience-on-notebook#public-preview-known-limitations

```pyhon
!pip install semantic-link-labs
```
:::note warn
data pipelineからの呼び出しでは **%pip**がエラーになるため、**!pip** を使用しています。本当は環境を作成して適用するのがよいです。
https://learn.microsoft.com/ja-jp/fabric/data-engineering/library-management#python-inline-installation
:::

`+新しい項目`から`環境`を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/e6f26936-cff6-a331-7b3b-c81b5654f220.png)

こんな感じで設定して。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/3a6f8738-b81f-3b74-198b-95414f9287e0.png)

ノートブックの環境で選択するとインラインでのインストールは不要になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/6a00d8b8-24f9-b4af-1ad1-8d08275973fe.png)

以下、ノートブックでのセルの内容です

```python
import sempy.fabric as fabric
```

```python
workspace_name = "fabric_exercise"
model_name_prefix = "Contoso"
```
セマンティックモデル名のプレフィックスとワークスペース名をセットします。
このセルを **parameter cell** に設定します。これでData pipelineから渡されたパラメーターを上書きできるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/f79b1aaa-8e8b-0296-510d-51bab78ee36b.png)

```python
# セマンティックモデルのリストを取得（ワークスペースの指定がない場合はNotebookが保存されているワークスペースが範囲）
if workspace_name:
    df_datasets = fabric.list_datasets(workspace=workspace_name)
else:
    df_datasets = fabric.list_datasets()

# 先頭一致でセマンティックモデルをフィルタリング（指定がない場合は該当ワークスペース全体のセマンティックモデルが対象）
if model_name_prefix:
    filtered_df = df_datasets[df_datasets["Dataset Name"].str.startswith(model_name_prefix)]
else:
    filtered_df = df_datasets

# "Dataset ID"と"Dataset Name"のペアをリストに変換
filtered_dataset_info = filtered_df[["Dataset ID", "Dataset Name"]].values.tolist()

# データセットIDとデータセット名のペアをnotebook.exitで返す
notebookutils.notebook.exit(filtered_dataset_info)
```
最後のステップのnotebookutilsでパイプラインに値を返します。
※なんとなく汎用性を考えてIDと名称をとっています。


#### セマンティックモデル内のメジャーを一括更新
:::note info
こちらは戻り値が不要だったので通常のPythonにしました。SparkよりCUが節約できますね。
:::

```python
!pip install semantic-link-labs
```
:::note warn
ノートブックに環境を割り当てていればインストール不要です
:::


```python
import sempy_labs as sl
import sempy_labs.tom as tom
```
```python
workspace_name = 'fabric_exercise'
dataset_name = 'Contoso'
```
上のセルを **parameter cell** に設定します。

```python
# モデルに接続し読み書き可能に設定
with tom.connect_semantic_model(dataset_name, readonly=False, workspace=workspace_name) as model:
    # メジャーの一覧を取得
    measures = model.all_measures()
    
    # メジャーの名前と定義を取得し、Descriptionにセットして更新
    for measure in measures:
        definition = measure.Expression
        model.update_measure(measure_name=measure.Name, description=definition)
        print(f"Updated Measure: {measure.Name}")
        print(f"  New Description: {definition}")
```

:::note info
pipelineでの実行時のデバッグのために、print文を追加しています。
:::



### Data pipeline
Data pipelineを作成します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/37f2b4c3-81c1-e84c-a168-5c87a28e8d44.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/37951e0c-499c-0e02-ee39-7283422f52fb.png)


#### ノートブックアクティビティの追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/84501af7-c7ee-5c50-e1b0-63c996b168d8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/e7e65137-0af8-f44f-dcd9-29fc7a6665ba.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/74064d42-f117-0a64-3c79-25c89e6d0676.png)


#### パラメタの追加
キャンバスの空白部分を選択して、パラメタタブからワークスペース名とセマンティックモデル名のプレフィックスのパラメタを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/2caeda99-5c10-f3ac-363a-15e9897548c1.png)

#### ノートブックアクティビティにノートブックを選択しベースパラメーターを追加
1. ノートブックを選択
1. `＋新規`からベースパラメタを追加
1. 名前を入力
1. 種類で`文字列`を選択
1. `動的なコンテンツの追加[Alt+Shift+D]`を押下
1. `パラメータ`を選択
1. `OK`を押下

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/905b8ab3-0f69-ebf1-9c7e-82987daee05a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/8777be60-aa1f-918a-70ad-d4efb59eead7.png)

#### 一旦、実行結果を確認する
次のアクティビティでのパラメタの設定を検討するために一旦実行して結果を確認します。

1. ホームから`実行`を押下
1. パラメータを設定
1. `OK`を押下

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/777a3434-0a67-271e-c1ce-d16af987e2b8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/504a9e13-b69d-3aa7-bf48-cad6f8761aa7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/0fa4469e-b656-96ab-378b-f0cfc21e4935.png)

`出力`をクリックし出力結果のJSONを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/982a0eef-3505-b54d-8935-cc1a53f6c9f2.png)

結果は以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/596a50c8-e0ec-1bba-d94c-a7500db16a70.png)

アクティビティの結果からexitValueを取得するために、以下のような記載をします。
JSONの階層を`.`で繋ぎます
**activity('getSemanticModel_IDs').output.result.exitValue**

#### ForEachアクティビティを追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/46f9716c-dfd2-f458-2bba-a5214bf49d0f.png)

#### アクティビティを連結
成功時のチェックボックスの連結部をドラッグしてForEachのアクティビティにドラッグして連結します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/2d3e1953-bbd2-3891-7d61-108dbd79e7c2.png)



#### ForEachの項目を設定

`動的なコンテンツの追加[Alt+Shift+D]`からアクティビティの結果をJSON形式で設定します
```json
@json(activity('getSemanticModel_IDs').output.result.exitValue)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/9372d69f-fddb-601e-8beb-f9361f86b115.png)

#### ForEachの中にノートブックアクティビティを追加
1. ForEachの編集ボタンを押下
1. アクティビティからNotebookを押下
1. ノートブックを設定し、ベースパラメーターを追加します
1. workspace_nameはパラメーターから設定
1. dataset_nameには繰り返しのアイテムを設定
:::note info
`@item()[1]`の[1]はexitValueのリストの２番目の項目（セマンティックモデル名）を意味しています。
::: 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/c234fe3e-d1fa-a5f1-0868-45afe3ea254b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/74e62d32-5cca-7f3b-47f7-c1ff67d108e0.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/56ac3ec4-e232-b051-98cb-11a86a629f56.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/5781a576-224d-3bed-73c9-c754a22dcbde.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/0039e3fc-80d1-b320-676e-791d7cfcabce.png)

#### ノートブックアクティビティにセッションタグを追加
セッションタグを追加することでForEach内でで実行されるノートブックが同一セッション内で実行されます。

1. ワークスペースの設定でPipelineの高コンカレンシーの設定を有効にします。
1. ForEach内のノートブックの設定でセッションタグを設定します
任意の文字列でOKですが、今回はシステム変数のPipeline Nameを使用しています。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/dd39843c-d430-17a1-18e6-4a03819d15f9.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/31d80609-a050-90ab-98c8-19819c7cf565.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/3dbf1c24-5c87-fe1e-8c3c-c99ccf26b9a4.png)

#### Data Pipelineの同時実行数を「1」に設定します
キャンバスの空白をクリックし、設定から`コンカレンシー`を「1」に設定します。

:::note info
この設定で誤って同時実行されることがなくなります。
:::


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/20935be8-d993-5131-d690-58afd63e8198.png)

#### 完成！実行してみます・・・！
1. `実行`をクリックし、パラメーターを設定して、`OK`を押下します。
1. 実行時にpipelineから渡したパラメーターで上書きされたことが確認できます
1. print文の結果が確認できます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/98d6e069-3ca1-ffb3-b616-cdcc1d598cde.png)

成功したので実行結果を確認してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/32b6b41f-9a00-afdb-074d-9364b7926ad7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/1a25c210-5496-1cfa-0eda-4c7e014f5b55.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/5304eb15-1c49-226b-13bb-688181be3acb.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2783030/121b31d8-0bae-8d12-9d64-20c81195f131.png)

## おわりに
- fabricのアイテムを活用して自動化の実装が可能になりました
- Pythonの実装には生成AI様様です😂
- Data pipelineではJSONと仲良くなることが重要だと思いました


勉強を続けていきます！

https://powerbi.connpass.com/