> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livingsecurity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Power BI

> Power BI'ı Power Query ile Living Security Public API'sine bağlayın, /v1 üzerinde yıldız şemalı bir model oluşturun ve kimlik avı için operasyonel ile raporlama veri kümeleri arasında seçim yapın.

Bu kılavuz, `/v1` verilerinin Power BI'a okunmasına yönelik uçtan uca bir örnektir. Power Query (M) bağlantı modelini, `/v1/activities` üzerinde oluşturulmuş yıldız şemalı bir anlamsal modeli, artımlı yenilemeyi ve kişi başına kimlik avı sonuçları için **raporlama** önizleme veri kümelerinin ne zaman kullanılacağını kapsar.

Örneklerde ABD üretim ana bilgisayarı (`https://api.us-east-1.livingsecurity.ai/v1`) kullanılır. Bir AB kuruluşu için hem `Fn_FetchAll` hem de `Fn_GetJson` içinde `https://api.eu-west-1.livingsecurity.ai/v1` kullanın, ardından veri kaynağı kimlik bilgisini bu ana bilgisayara bağlayın. Bkz. [Bölgeler](/tr/developers/v1/regions).

Bu kılavuzda API anahtarıyla saklanan Basic kimlik bilgileri kullanılır. Bir OAuth istemcisine ihtiyacınız yoktur. Kimlik bilgisi seçenekleri için [Kimlik doğrulama](/tr/developers/v1/authentication) bölümüne, erişim ve kurulum için [Developer Hub](/tr/developers/developer-hub/overview) sayfasına bakın.

## Bu örneğin amacı

Aşağıdaki durumlarda kullanın:

* `/v1` liste uç noktalarını doğru imleç sayfalaması ve hız sınırı yeniden denemesiyle bir Power BI veri kümesine yüklemek.
* Ölçülerin ("etkin varlıklar", "etkin varlık başına etkinlikler") düz bir tablo üzerinden sayılmak yerine boyutsal olduğu bir anlamsal model oluşturmak.
* Zamanlanmış bir Power BI yenilemesini, artımlı yenileme kullanarak geçmişin tamamının taranmasından küçük bir günlük pencereye indirmek.
* Doğru kimlik avı ayrıntı düzeyini seçmek: kampanya toplamları (operasyonel) veya kişi başına sonuçlar ve türlendirilmiş olaylar (raporlama önizlemesi).

## Ön koşullar

* Sorgularınızın gerektirdiği izinlere sahip bir API anahtarı:
  * Operasyonel örnek: `activity:read`, `entity:read` ve `simulation:read`.
  * Raporlama veri kümeleri üzerinden kişi başına kimlik avı: `reporting:read`.
    Bkz. [API anahtarları](/tr/developers/developer-hub/api-keys).
* Power BI Desktop veya bir anlamsal model oluşturabileceğiniz ve yenileme zamanlayabileceğiniz bir Power BI / Fabric çalışma alanı.

## Doğru ayrıntı düzeyini seçin

`/v1`, kimlik avı için birbiriyle ilişkili iki yüzey sunar. Bunlar birbirinin yerine kullanılamaz.

| İhtiyaç | Kullanılacak | İzin | Ayrıntı düzeyi |
| - | - | - | - |
| Kampanya hunisi toplamları | `GET /v1/simulations/{id}/results` | `simulation:read` | Simülasyon başına bir satır (teslim edilen, açılan, tıklanan, …) |
| Kişi başına sonuçlar (operasyonel) | `GET /v1/simulation_results` | `simulation:read` | Varlık × simülasyon başına bir satır |
| Kişi başına sonuçlar (BI / raporlama) | `GET /v1/reporting/simulation_results` | `reporting:read` | Aynı ayrıntı düzeyi, BI alan adları (`entity_id`, `simulation_id`), `updated_after` filigranı |
| Türlendirilmiş etkileşim olguları | `GET /v1/reporting/simulation_events` | `reporting:read` | Etkileşim başına bir olgu (`simulation.link_clicked`, …), `occurred_after` |
| Güncel iş gücü boyutu | `GET /v1/reporting/entities` | `reporting:read` | Varlık başına bir güncel satır. `reporting.simulation_results.entity_id = reporting.entities.id` ile birleştirin |
| Güncel varlık sürümleri | `GET /v1/reporting/entity_versions` | `reporting:read` | Varlık başına bir açık uçlu güncel aralık. Sürüm geçmişi tablosu yoktur |
| Güncel Grup üyelikleri | `GET /v1/reporting/cohort_memberships` | `reporting:read` | `(cohort_id, entity_id)` başına bir güncel olgu. Üyelik geçmişi tablosu yoktur |
| Tüm etkinlik türleri için birleştirme omurgası | `GET /v1/activities` | `activity:read` | Etkinlik başına bir olay, iç içe `participants[]` |

Aşağıdaki Power BI örnek sorguları **operasyonel** rotaları (`/v1/simulations`, `/v1/simulations/{id}/results`, `/v1/activities`, `/v1/entities`) çağırır. Kişi başına kimlik avı raporlaması için aşağıdaki **raporlama** önizleme ailesini tercih edin. Yalnızca `reporting:read` izni verilmiş bir anahtar operasyonel `/v1/simulation_results` uç noktasını çağıramaz (API `403` döndürür).

### Zorluk ölçeği

Simülasyon zorluğu; `/v1/simulations` (`difficulty_level`), `/v1/simulation_results` (`difficulty_level`), `/v1/activities` özellikleri (`difficulty_tier`) ve `/v1/reporting/simulation_events` (`difficulty_tier`) üzerinde 1–5 arasında bir tam sayıdır. `1` en kolay, `5` ise en zor düzeydir. Uyarlanabilir ve aşamalı simülasyonların kampanya düzeyinde tek bir değeri yoktur; bu nedenle `/v1/simulations.difficulty_level` değeri `null` olurken sonuçlardaki alıcı başına `difficulty_level` yine de ayarlanabilir. Eski düşük/orta/yüksek dışa aktarımlarla eşlemek için `1–2` değerini düşük, `3` değerini orta ve `4–5` değerini yüksek olarak değerlendirin.

<Warning>
  Yeni BI entegrasyonlarını belgelenmemiş Kontrol Paneli ağ çağrıları veya eski, özenle hazırlanmış `/api/v1` Public API genel bakışı üzerine kurmayın. Kuruluşun bölgesel `/v1` ana bilgisayarında bir Developer Hub API anahtarı ile kimlik doğrulaması yapın. Bkz. [Bölgeler](/tr/developers/v1/regions).
</Warning>

## Bağlantı modeli (Power Query)

Örnekteki her sorgunun temelinde dört kural vardır. Her biri, tarz tercihleriyle değil Power BI Hizmeti tarafından zorunlu kılınır.

<AccordionGroup>
  <Accordion title="Web.Contents temel URL'sini statik tutun ve /v1 ile bitirin">
    Dinamik olan her şeyi `RelativePath` ve `Query` içine koyun. `Web.Contents` işlevinin ilk bağımsız değişkeni, kuruluşun bölgesel `/v1` köküne kadar olan sabit bir metin dizesi olmalıdır:

    * ABD: `https://api.us-east-1.livingsecurity.ai/v1`
    * AB: `https://api.eu-west-1.livingsecurity.ai/v1`

    Çağıranlar bu durumda yalnızca kaynağı iletir (`"v1/simulations"` değil, `"simulations"`). Bir AB kuruluşu için ABD ana bilgisayarını yapıştırmayın.

    İlk bağımsız değişken dize birleştirmesiyle oluşturulursa Power BI Hizmeti veri kaynağını statik olarak tanımlayamaz ve sorgu Power BI Desktop'ta çalışsa bile zamanlanmış yenileme "dynamic data source" hatasıyla başarısız olur.
  </Accordion>

  <Accordion title="API anahtarını sorgu metninde değil, saklanan Basic kimlik bilgisi olarak sağlayın">
    Power BI, `/v1` veri kaynağı için kimlik bilgisi istediğinde **Basic** seçeneğini seçin ve API anahtarını **username** olarak yapıştırın. **password** alanına `x` gibi bir yer tutucu girin. API bu alanı yok saysa da Power BI Hizmeti boş bir şifreyi reddeder. Anahtarın şifre, yer tutucunun kullanıcı adı olarak girilmesi de çalışır.

    M içinde bir `Authorization` üst bilgisi eklemeyin. Masaüstünüzde çalışır, ancak anahtarı sorgu dosyasına gömer ve zamanlanmış yenilemeyle çakışır.
  </Accordion>

  <Accordion title="Kimlik bilgisi iletişim kutusunun bağlantı testinin çalışmasına izin verin">
    `GET /v1` kimlik doğrulamalıdır: geçerli bir anahtarla `200`, anahtarsız `401` döndürür. Veri kaynağını `…/v1` adresine yönlendirin ve testi **atlamayın** — geçerli bir anahtar kaydedilir, iptal edilmiş veya hatalı biçimlendirilmiş bir anahtar ise hemen başarısız olur.

    Eski örneklerde veri kaynağı olarak yalın ana bilgisayar kullanılıyor ve `GET /` her zaman `404` döndürdüğü için testin atlanması söyleniyordu. Bu modelleri `…/v1` adresine yeniden yönlendirin.
  </Accordion>

  <Accordion title="Her liste sorgusunda limit=500 değerini açıkça ayarlayın">
    Her `/v1` liste uç noktasındaki varsayılan sayfa boyutu 25'tir. Varsayılanın bırakılması, aynı veri için istek sayısını katlar. Sorgu değerleri metin olmalıdır — M içinde `limit = 500` değil, `limit = "500"`.
  </Accordion>
</AccordionGroup>

### Ortak sayfalama işlevi

Örnekteki her sorgu, `next_page_token` değeri `null` olana kadar ilerleyen tek bir `Fn_FetchAll` işlevini çağırır (bkz. [sayfalama](/tr/developers/v1/pagination)). Dikkat çeken özellikleri:

* **Statik `/v1` temel URL'si, dinamik `RelativePath` ve `Query`.** Zamanlanmış yenileme ve kimlik bilgisi testi için gereklidir.
* **Sınırlı yeniden denemeli `ManualStatusHandling = {429}`.** İşlev, Power Query sunduğunda `Retry-After` değerini okur, aksi halde 30 saniye bekler ve yenileme zaman aşımına uğrayana kadar döngüde kalmak yerine art arda 5 `429` yanıtından sonra açık bir hatayla vazgeçer. `5xx` kasıtlı olarak yeniden denenmez — bir sunucu hatası sessizce kısmi bir veri kümesi üretmek yerine görünür olmalıdır.
* **60 saniyelik `Timeout`.** Sayfalar genellikle bir saniyenin çok altında yanıt verir; bu nedenle bir dakikalık sessizlik isteğin öldüğü anlamına gelir.
* **`Json.Document` öncesinde `Binary.Buffer`.** Arabelleğe alma olmadan, mashup altyapısı yanıt akışını birden fazla kez okuyorsa isteği yeniden yapabilir.
* **`baseQuery` her yinelemede değiştirilmeden iletilir.** Sayfa belirteçleri; kuruluşa, kimlik bilgisine, sözleşme sürümüne ve filtre ile sıralama parmak izine bağlı, opak ve imzalı imleçlerdir. Dolaşım sırasında bir filtreyi değiştirmek, ilk sayfadan yeniden başlatılmak yerine geçersiz belirteç olarak reddedilir.

```m Fn_FetchAll.pq — base and call shape theme={null}
let
    // Literal /v1 root. Callers pass the resource alone ("simulations").
    // US: "https://api.us-east-1.livingsecurity.ai/v1"
    BaseUrl = "https://api.eu-west-1.livingsecurity.ai/v1",

    Fn_FetchAll = (relativePath as text, baseQuery as record) as list =>
        // Walk next_page_token until null. Full pagination + 429 retry lives
        // in examples/powerbi/queries/Fn_FetchAll.pq.
        ...
in
    Fn_FetchAll
```

### Uç nokta başına türlendirilmiş sorgular

`Fn_FetchAll` işlevinin her tüketicisi aynı yapıyı izler: uç noktayı çağırın, sabit bir sütun listesi yansıtın, `object` ayırt edicisini kaldırın ve zaman damgalarını `datetimezone` olarak türlendirin; böylece tarih ölçüleri yenileme kapasitesinin saat dilimine göre sessizce kaymaz.

<CodeGroup>
  ```m Simulations.pq (GET /v1/simulations) theme={null}
  let
      Source = Fn_FetchAll("simulations", [ limit = "500" ]),

      AsTable = Table.FromRecords(
          Source,
          {
              "id", "object", "name", "description", "status", "vectors",
              "difficulty_level", "difficulty_mode",
              "started_at", "completed_at", "created_at", "updated_at"
          },
          MissingField.UseNull),

      Dropped = Table.RemoveColumns(AsTable, { "object" }),

      Flattened = Table.TransformColumns(
          Dropped,
          {
              {
                  "vectors",
                  each if _ = null then null
                       else Text.Combine(List.Transform(_, Text.From), ", "),
                  type text
              }
          }),

      Typed = Table.TransformColumnTypes(
          Flattened,
          {
              { "id",           type text },
              { "name",         type text },
              { "description",  type text },
              { "status",       type text },
              { "started_at",   type datetimezone },
              { "completed_at", type datetimezone },
              { "created_at",   type datetimezone },
              { "updated_at",   type datetimezone }
          })
  in
      Typed
  ```

  ```m Entities.pq (GET /v1/entities) theme={null}
  let
      Source = Fn_FetchAll("entities", [ limit = "500" ]),

      AsTable = Table.FromRecords(
          Source,
          { "id", "object", "type", "status", "display_name", "email",
            "created_at", "updated_at" },
          MissingField.UseNull),

      Dropped = Table.RemoveColumns(AsTable, { "object" }),

      Typed = Table.TransformColumnTypes(
          Dropped,
          {
              { "id",           type text },
              { "type",         type text },
              { "status",       type text },
              { "display_name", type text },
              { "email",        type text },
              { "created_at",   type datetimezone },
              { "updated_at",   type datetimezone }
          })
  in
      Typed
  ```
</CodeGroup>

<Tip>
  `Table.FromRecords` üzerinde açık bir sütun listesi, bir uç nokta sıfır satır döndürdüğünde de şemayı kararlı tutar. Yalın bir `Table.FromRecords({})` sıfır sütunlu bir tablo üretir ve sonraki her adım eksik alanlar nedeniyle başarısız olur.
</Tip>

## Excel

Aynı Power Query (M), Excel'in Get & Transform özelliğinde de çalışır — Power BI Desktop ile aynı altyapıdır. Anlamsal model yerine bir çalışma sayfası tablosu istediğinizde bunu kullanın.

Windows için Excel'de (Microsoft 365, Excel 2024, 2021, 2019 veya 2016):

1. **Data → Get Data → From Other Sources → Blank Query.**
2. Power Query Editor'da **Home → Gelişmiş Düzenleyici** seçeneğine gidin. Önce `Fn_FetchAll` kodunu yapıştırın, ardından bu sorguyu `Fn_FetchAll` olarak yeniden adlandırın (Query Settings → Name veya Queries bölmesinde sağ tıklayıp **Rename**).
3. `Fn_GetJson` için ve ardından her rapor sorgusu için tekrarlayın.
4. **Home → Close & Load → Close & Load To…** seçeneği **Import Data** iletişim kutusunu açar. `Fn_FetchAll` ve `Fn_GetJson` için **Only Create Connection** seçeneğini belirleyin (bunlar tablo değil, işlevdir). Çalışma sayfasında görmek istediğiniz her rapor sorgusu için **Table** seçeneğini belirleyin.
5. İlk yenilemede **Basic** seçeneğini belirleyin: API anahtarını **User name** olarak, `x` gibi bir yer tutucuyu **Password** olarak girin. Excel, veri kaynağı URL'si başına bir kimlik bilgisi saklar; aynı `/v1` ana bilgisayarına erişen sonraki sorgular bu kimlik bilgisini yeniden kullanır.

<Note>
  Bir çalışma kitabında ilk sorguyu oluşturduğunuzda **Close & Load To…** seçeneği soluk görünebilir. **Close & Load** seçeneğini kullanın, ardından Excel'de **Data → Queries & Connections** yolunu izleyin, sorguya sağ tıklayın ve **Load To** seçeneğini belirleyin. Bu yol, bu durum için Microsoft tarafından belgelenmiştir.
</Note>

Kimlik doğrulamalı bir kaynakta Power Query'den `Living-Security-Version` gönderemezsiniz — Power BI ile aynı kısıtlamadır. Hizmet hesabını sabitleyin veya sorgu kaydında `api_version` iletin. Bkz. [Sözleşme sürümleri ve saklanan kimlik bilgileri](#sözleşme-sürümleri-ve-saklanan-kimlik-bilgileri).

Hazır rapor sorguları (sütun eşlemeleri ve önceki bir sağlayıcının dışa aktarımından farklılıklar):

<CardGroup cols={3}>
  <Card title="Kişi başına uyumluluk" href="/tr/developers/v1/reports/per-person-compliance">
    Kişi × atama ayrıntı düzeyinde Eğitim kayıtları.
  </Card>

  <Card title="E-posta kimlik avı günlüğü" href="/tr/developers/v1/reports/email-phishing-log">
    Olay düzeyinde kimlik avı etkileşimleri, son 12 ay.
  </Card>

  <Card title="Saldırıya en açık kullanıcılar" href="/tr/developers/v1/reports/most-susceptible-users">
    Kişi başına 12 aylık tıklama, kimlik bilgisi ve bildirim özeti.
  </Card>
</CardGroup>

## Raporlama önizleme veri kümeleri (kişi başına kimlik avı)

Raporlama önizleme ailesi, kişi başına kimlik avı için veri kümeleri sağlar. `reporting:read` ve kuruluşunuz için raporlama erişimi gerektirir. Şemalarını ve veri politikalarını incelemek için aşağıdaki veri kümesi kataloğunu kullanın.

Canlı kataloğu keşfedin:

```text theme={null}
GET /v1/reporting/datasets
GET /v1/reporting/datasets/simulation_results
GET /v1/reporting/datasets/simulation_events
GET /v1/reporting/datasets/entities
GET /v1/reporting/datasets/entity_versions
GET /v1/reporting/datasets/cohort_memberships
```

Her veri kümesi; ayrıntı düzeyini, birincil anahtarı, birleştirmeleri, filigranı, düzeltme ve silme politikasını bildirir.

### simulation\_results

* **Yol:** `GET /v1/reporting/simulation_results`
* **Ayrıntı düzeyi:** varlık × simülasyon
* **Filtreler:** `simulation`, `entity`, `updated_after`, ayrıca `limit` / `page_token`
* **Ağ nesnesi:** `reporting.simulation_result`
* **Birleştirme anahtarları:** `entity_id`, `simulation_id`

```m ReportingSimulationResults.pq (sketch) theme={null}
let
    Source = Fn_FetchAll(
        "reporting/simulation_results",
        [ limit = "500" ]),

    AsTable = Table.FromRecords(
        Source,
        {
            "id", "object", "entity_id", "simulation_id", "status",
            "targeted_at", "delivered_at", "opened_at", "clicked_at",
            "credential_submitted_at", "reported_at",
            "email_subject", "sender_email", "difficulty_level",
            "created_at", "updated_at", "deleted_at"
        },
        MissingField.UseNull),

    Dropped = Table.RemoveColumns(AsTable, { "object" }),

    Typed = Table.TransformColumnTypes(
        Dropped,
        {
            { "id",            type text },
            { "entity_id",     type text },
            { "simulation_id", type text },
            { "status",        type text },
            { "targeted_at",   type datetimezone },
            { "updated_at",    type datetimezone }
        })
in
    Typed
```

Son bölümleri yenilerken `updated_after` değerini dahil alt filigran olarak kullanın. Değişebilir sonuç boyutları aynı `id` altında yerinde yeniden belirtilir.

### simulation\_events

* **Yol:** `GET /v1/reporting/simulation_events`
* **Ayrıntı düzeyi:** gözlemlenen her etkileşim için türlendirilmiş bir olgu
* **Filtreler:** `simulation`, `entity`, `event_type`, `occurred_after`, ayrıca `limit` / `page_token`
* **Ağ nesnesi:** `reporting.simulation_event`
* **Olay türleri:** `simulation.delivered`, `simulation.opened`, `simulation.link_clicked`, `simulation.credentials_submitted`, `simulation.reported` gibi kapalı genel belirteçler — ham ClickHouse `phishing.simulation.*` etkinlik türleri değil
* **Cihazı:** `device_type` türetilmiş sınıftır (gözlemlenen belirteçler arasında `desktop`, `mobile`, `tablet`, `unknown` bulunur). Ham `user_agent` değeri `/v1` üzerinde yer almaz. Ham IP hiçbir zaman `/v1` üzerinde yer almaz (asla kalıcı olarak saklanmaz; yalnızca tuzlanmış `ip_hash`).

```m ReportingSimulationEvents.pq (sketch) theme={null}
let
    Source = Fn_FetchAll(
        "reporting/simulation_events",
        [ limit = "500" ]),

    AsTable = Table.FromRecords(
        Source,
        {
            "id", "object", "entity_id", "simulation_id",
            "event_type", "occurred_at",
            "email_subject", "sender_email", "sender_domain", "difficulty_tier",
            "device_type",
            "deleted_at"
        },
        MissingField.UseNull),

    Dropped = Table.RemoveColumns(AsTable, { "object" }),

    Typed = Table.TransformColumnTypes(
        Dropped,
        {
            { "id",            type text },
            { "entity_id",     type text },
            { "simulation_id", type text },
            { "event_type",    type text },
            { "occurred_at",   type datetimezone }
        })
in
    Typed
```

Artımlı olay yüklemeleri için `occurred_after` kullanın. Olgular yalnızca eklenir; geç gelen kayıtlar yeni satırlardır.

### entities

* **Yol:** `GET /v1/reporting/entities`
* **Ayrıntı düzeyi:** varlık başına bir güncel satır
* **Filtreler:** `type`, `updated_after`, ayrıca `limit` / `page_token`
* **Ağ nesnesi:** `reporting.entity`
* **Birleştirme anahtarı:** `id` / `entity_id` (aynı değer)

Kimlik avı olgularını kişilerle `reporting.simulation_results.entity_id = reporting.entities.id` ile birleştirin. Operasyonel `GET /v1/entities` `entity:read` üzerinde kalır ve farklı bir izindir.

```m ReportingEntities.pq theme={null}
let
    Source = Fn_FetchAll("reporting/entities", [ limit = "500" ]),

    AsTable = Table.FromRecords(
        Source,
        {
            "id", "object", "entity_id", "type", "status",
            "display_name", "created_at", "updated_at", "deleted_at"
        },
        MissingField.UseNull),

    Dropped = Table.RemoveColumns(AsTable, { "object" }),

    Typed = Table.TransformColumnTypes(
        Dropped,
        {
            { "id",          type text },
            { "entity_id",   type text },
            { "type",        type text },
            { "status",      type text },
            { "display_name", type text },
            { "created_at",  type datetimezone },
            { "updated_at",  type datetimezone }
        })
in
    Typed
```

### entity\_versions ve cohort\_memberships

Bu iki veri kümesi, yalnızca güncel durumu içeren adlandırılmış bir istisna taşır:

> Yetkili bir sürüm veya üyelik geçmişi tablosu mevcut değildir. Bu veri kümesi geçerli satırı tek bir açık uçlu aralık olarak yansıtır (`valid_to` null, `is_current` true). `valid_from`, varlık sürümlerinde `created_at` veya aksi halde `updated_at` değeridir; Grup üyeliklerinde ise istek gözlem zamanıdır. Kapalı geçmiş aralıklar uydurulmaz.

* **entity\_versions:** `GET /v1/reporting/entity_versions`. Filtreler entities ile aynıdır. `valid_from`, `created_at` veya aksi halde `updated_at` değeridir. Her iki zaman damgası da null olan bir varlık dışarıda bırakılır.
* **cohort\_memberships:** `GET /v1/reporting/cohort_memberships`. Filtreler: `cohort`, `entity`, `updated_after`. `updated_at`, artımlı yenilemenin doğru kalması için Grubun son güncelleme zamanıdır. `valid_from` istek gözlem zamanıdır. `entity_id` değerini `reporting.entities.id` ile birleştirin.

```m ReportingEntityVersions.pq theme={null}
let
    Source = Fn_FetchAll("reporting/entity_versions", [ limit = "500" ]),

    AsTable = Table.FromRecords(
        Source,
        {
            "id", "object", "entity_id", "type", "status", "display_name",
            "valid_from", "valid_to", "is_current",
            "created_at", "updated_at", "deleted_at"
        },
        MissingField.UseNull),

    Dropped = Table.RemoveColumns(AsTable, { "object" }),

    Typed = Table.TransformColumnTypes(
        Dropped,
        {
            { "id",         type text },
            { "entity_id",  type text },
            { "valid_from", type datetimezone },
            { "is_current", type logical }
        })
in
    Typed
```

```m ReportingCohortMemberships.pq theme={null}
let
    Source = Fn_FetchAll("reporting/cohort_memberships", [ limit = "500" ]),

    AsTable = Table.FromRecords(
        Source,
        {
            "id", "object", "cohort_id", "entity_id",
            "valid_from", "valid_to", "is_current",
            "updated_at", "deleted_at"
        },
        MissingField.UseNull),

    Dropped = Table.RemoveColumns(AsTable, { "object" }),

    Typed = Table.TransformColumnTypes(
        Dropped,
        {
            { "id",         type text },
            { "cohort_id",  type text },
            { "entity_id",  type text },
            { "valid_from", type datetimezone },
            { "is_current", type logical },
            { "updated_at", type datetimezone }
        })
in
    Typed
```

## /v1/activities üzerinde yıldız şemalı anlamsal model

`/v1/activities`, kuruluşun zaman damgalı olay akışıdır. Sayısal ölçüleri yoktur — bir raporlardaki her nicelik satır sayısıdır — bu da yıldız şemayı doğal bir seçim yapar.

```text theme={null}
Dates (dimension)
      │  1
      ▼  ∗
  Activities (fact)   1  ∗  ActivityParticipants (bridge)   ∗  1   Entities (dimension)
```

* **`Activities`**, gerçek ayrıntı düzeyindeki olay olgusudur. Etkinlik başına bir satır. Katılımcılar burada çıkarılır ve kendi tablosu olarak yeniden oluşturulur.
* **`ActivityParticipants`**, katılımcılar gerçekten bire-çok olduğu için vardır; `actor` ve `target` gibi rolleri bulunur. `Activities` üzerine düzleştirmek ya etkinlik satırlarını çoğaltır (her sayımı şişirir) ya da ek katılımcıları atar. `Activities` ile ilişki, modeldeki tek çift yönlü ilişkidir; böylece bir varlık dilimleyicisi olgu tablosuna ulaşır.
* **`Entities`**, katılımcıları adlandıran boyuttur. Bir etkinlikteki katılımcı satırları `entity_id` ve bir rol taşır, ancak görünen ad taşımaz.
* **`Dates`** M içinde üretilir (sıfır API çağrısı) ve modelin tarih tablosu olarak işaretlenir. DAX zaman istihbaratının (`DATESINPERIOD`, `DATEADD`) geçerli olmasını sağlayan budur. Ay ve haftanın günü adı sütunları, görsellerin ayları alfabetik sıralamaması için sayı eşlerinde `sortByColumn` bildirir.

Tüm ölçüler açıktır (model `discourageImplicitMeasures` ayarını yapar): toplam etkinlikler, etkin varlıklar, etkin varlık başına etkinlikler, etkinlik olan günler ve 7 günlük geriye dönük günlük ortalama.

### Köprü tablosu yapısı

`ActivityParticipants`, `/v1/activities` uç noktasını tarar ve `participants` dizisini katılımcı başına bir satıra açar:

```m ActivityParticipants.pq theme={null}
let
    WindowStart = DateTime.AddZone(RangeStart, 0),
    WindowEnd   = DateTime.AddZone(RangeEnd, 0),
    ToIso = (value as datetimezone) as text =>
        DateTimeZone.ToText(value,
            [ Format = "yyyy-MM-dd'T'HH:mm:ss'Z'", Culture = "en-US" ]),

    Source = Fn_FetchAll(
        "activities",
        [
            limit      = "100",
            start_date = ToIso(WindowStart - #duration(0, 0, 1, 0)),
            end_date   = ToIso(WindowEnd + #duration(0, 0, 1, 0))
        ]),

    AsTable = Table.FromRecords(
        Source,
        { "id", "object", "activity_type", "event_ts",
          "service_id", "participants" },
        MissingField.UseNull),

    Selected = Table.SelectColumns(AsTable, { "id", "event_ts", "participants" }),
    Renamed  = Table.RenameColumns(Selected, { { "id", "activity_id" } }),
    Expanded = Table.ExpandListColumn(Renamed, "participants"),
    NonNull  = Table.SelectRows(Expanded, each [participants] <> null),
    Fields   = Table.ExpandRecordColumn(
        NonNull, "participants",
        { "role", "entity_id", "display_name" }),

    Typed = Table.TransformColumnTypes(
        Fields,
        {
            { "activity_id",  type text },
            { "event_ts",     type datetimezone },
            { "role",         type text },
            { "entity_id",    type text },
            { "display_name", type text }
        }),

    Filtered = Table.SelectRows(
        Typed,
        each [event_ts] >= WindowStart and [event_ts] < WindowEnd)
in
    Filtered
```

`ActivityParticipants`, `/v1/activities` uç noktasını `Activities` sorgusuna başvurmak yerine bağımsız olarak tarar. Power BI'da iki tablo olarak yüklenen iki sorgu, yenileme sırasında ayrı ayrı veri çeker. Bunları bağımsız tutmak, hizmetin her iki tablonun bölümlerini paralel yenilemesine olanak tanır.

## Artımlı yenileme

Her iki olgu ayrıntı düzeyli tablo (`Activities` ve `ActivityParticipants`) da bir Power BI artımlı yenileme politikası bildirir: 5 yıllık yıllık bölümleri tutun, her yenilemede son 14 günü yeniden içe aktarın.

Mekanizma, Power BI hizmetinin yenileme sırasında bölüm başına yeniden bağladığı bir çift DateTime parametresidir: `RangeStart` ve `RangeEnd`. Her olgu sorgusu bu parametreleri API'nin `start_date` ve `end_date` filtrelerine yansıtır; böylece bölüm düzeyindeki istek hacmi toplam geçmişle değil, bölümle orantılı olur. Sorgu, API penceresini her iki yanda bir dakika genişletir ve tam yarı açık `[RangeStart, RangeEnd)` sınırını yerel olarak yeniden uygular; böylece bölüm doğruluğu, sunucunun sınırlarını dahil mi yoksa hariç mi değerlendirdiğine bağlı olmaz.

```m Activities.pq — the fold that makes incremental refresh work theme={null}
let
    WindowStart = DateTime.AddZone(RangeStart, 0),
    WindowEnd   = DateTime.AddZone(RangeEnd, 0),
    ToIso = (value as datetimezone) as text =>
        DateTimeZone.ToText(value,
            [ Format = "yyyy-MM-dd'T'HH:mm:ss'Z'", Culture = "en-US" ]),

    Source = Fn_FetchAll(
        "activities",
        [
            limit      = "100",
            start_date = ToIso(WindowStart - #duration(0, 0, 1, 0)),
            end_date   = ToIso(WindowEnd + #duration(0, 0, 1, 0))
        ]),

    AsTable = Table.FromRecords(
        Source,
        { "id", "object", "activity_type", "event_ts",
          "service_id", "participants" },
        MissingField.UseNull),

    Dropped = Table.RemoveColumns(AsTable, { "object", "participants" }),

    Typed = Table.TransformColumnTypes(
        Dropped,
        {
            { "id",            type text },
            { "activity_type", type text },
            { "event_ts",      type datetimezone },
            { "service_id",    type text }
        }),

    Filtered = Table.SelectRows(
        Typed,
        each [event_ts] >= WindowStart and [event_ts] < WindowEnd),

    WithDate = Table.AddColumn(
        Filtered, "event_date", each Date.From([event_ts]), type date)
in
    WithDate
```

Bir dağıtımdan sonraki ilk yenileme politikayı uygular: tam bir geçmiş yüklemesi, yavaştır. Sonraki her yenileme yalnızca artımlı pencereye dokunur.

Yaklaşık 158.000 etkinlik ve yaklaşık 2.300 varlıktan oluşan bir veri kümesinde ölçülen değerler:

| Yenileme | Geçen süre |
| - | - |
| İlk (politika uygulaması, tam geçmiş yüklemesi) | \~31 dk |
| Sonraki her yenileme | **\~15 sn** |

<Warning>
  Power BI klasik yenileme REST uç noktası (yalnızca `notifyOption` ile `POST .../refreshes`) yenileme politikalarını sessizce yok sayar ve her seferinde tam tarama yapar. Politikayı REST API üzerinden uygulamak için gelişmiş yenilemeyi kullanın: gövdede `type`, `commitMode` ve `applyRefreshPolicy` gönderin. Power BI Hizmeti zamanlayıcısı tarafından tetiklenen yenilemeler politikaları her zaman uygular — bu tuhaflık yalnızca REST çağrısına özgüdür.
</Warning>

<Note>
  14 günlük pencere kasıtlı olarak geniş tutulmuştur. Etkinlik alımı, yalnızca o anda eklenen türde değildir: bağlayıcı geri doldurmaları olayları geçmiş zaman damgalarıyla getirir. 1-2 günlük bir pencere bunları kalıcı olarak kaçırır.
</Note>

## Yenileme penceresinin API'ye genişletilmesi

`GET /v1/activities`, Power BI parametrelerinin yansıtıldığı `start_date` ve `end_date` filtrelerini kabul eder ve `event_ts` değerini sınırlar arasında filtreler. `Entities` bir boyuttur; bu nedenle bu örnek onu artımlı olarak değil, her seferinde tam olarak yeniler.

Raporlama veri kümeleri, etkinlikler tarih penceresi yerine kendi filigranlarını (`updated_after`, `occurred_after`) kullanır.

## Yenileme sırasındaki hız sınırları

`/v1`, anahtar başına ve IP başına ayrı bütçeler uygular (bkz. [hız sınırları](/tr/developers/v1/rate-limits)). `429` + `Retry-After` değerlerine uyun; modelinize sabit bir istek bütçesi kodlamayın. Power BI yenilemelerinin iki özelliği önemlidir:

* **Tek bir tablo taraması ardışıktır.** Her sayfa isteği bir önceki sayfanın belirtecine ihtiyaç duyar; dolayısıyla sayfa boyutundan bağımsız olarak tek bir tablo gecikmeyle sınırlıdır.
* **Eşzamanlı tablo yenilemeleri katlanır.** Birkaç büyük tablonun paralel yenilenmesi tavana ulaşılan durumdur; `Fn_FetchAll` içindeki 429 yeniden deneme yolunun önemi bu yüzdendir. Sınırsız eşzamanlılık varsaymak yerine daha az paralel tabloyu tercih edin.

Yeniden deneme, gerçek bir doygun sınırlayıcıya karşı denenmiştir. Paralel bir iş yükünün anahtarı kasıtlı olarak tavanında tuttuğu sırada çalışan bir yenileme yine de tamamlanmış ve çekişmesiz bir temel ölçümle aynı satır sayılarını üretmiştir — yeniden denemeler boşluk değil, eksiksiz sayfalar döndürmüştür.

## Sözleşme sürümleri ve saklanan kimlik bilgileri

`/v1`, yayımlanmış bir sözleşme sürümünü sabitlemek için bir `Living-Security-Version` istek üst bilgisi kabul eder (bkz. [API hızlı başlangıç](/tr/developers/v1/quickstart)). Veri kaynağı saklanan kimlik bilgilerini kullandığında Power Query bu üst bilgiyi gönderemez — yalnızca anonim veri kaynakları özel üst bilgilere izin verir. Eklenmesi, yenilemenin şu hatayla başarısız olmasına neden olur:

```text theme={null}
Expression.Error: The 'Living-Security-Version' header is only supported when
connecting anonymously.
Microsoft.Data.Mashup.ErrorCode = 10348.
```

Anahtarı Power BI'ın saklanan kimlik bilgilerinde tutun ve özel üst bilgiyi atlayın. İstekte açık bir sürüm yoksa API, hizmet hesabının sabitlenmiş `contract_version` sürümünü, sabitleme ayarlanmamışsa yayımlanmış en son sürümü kullanır. Bir yenileme yayımlamadan önce hesabın sözleşme sürümünü API yöneticinizle doğrulayın. Desteklenen sürümler için [API değişiklik günlüğüne](/tr/developers/v1/changelog) bakın.

Bir yenileme beklenmedik şekilde davranmaya başlarsa, isteğe hangi sözleşmenin hizmet verdiğini görmek için API'nin `Living-Security-Version` yanıt üst bilgisini inceleyin.

## Modelinizi yenileyin ve doğrulayın

Anlamsal modeli Power BI çalışma alanınıza yayımlayın, saklanan kimlik bilgisini bölgesel `/v1` kaynağına bağlayın ve bir zamanlamayı etkinleştirmeden önce bir yenileme çalıştırın. Hatalar veya eksik okumalar için yenileme geçmişini inceleyin.

Satır sayılarını, aynı filtreleri kullanan bağımsız bir API okumasıyla karşılaştırın. [Sayfalama kılavuzu](/tr/developers/v1/pagination), eksiksiz bir sonuç kümesini okumak için Python ve cURL örnekleri içerir. Yalnızca ilk sayfayı okuyan bir yenileme, küçük bir veri kümesinde başarılı görünebilir; rapora güvenmeden önce sayıları karşılaştırın.

## Model seçimleri

* **Kampanya toplamları ve kişi başına sonuçların ayrıntı düzeyleri farklıdır.** `SimulationResults`, `GET /v1/simulations/{id}/results` uç noktasını çağırır (çalıştırma başına bir GET). Kişi başına kimlik avı için `GET /v1/reporting/simulation_results` ve `GET /v1/reporting/simulation_events` (veya zaten `simulation:read` iznine sahipseniz operasyonel `GET /v1/simulation_results`) için sorgular ekleyin.
* **`/v1/simulations` üzerindeki `vectors`, virgülle birleştirilmiş bir dizeye düzleştirilir.** Liste türündeki bir sütun gerçek bir boyut olarak dilimlenemez. Bir rapor sayfasının vektöre göre filtreleme yapması gerekiyorsa bir simülasyon-vektör köprü tablosu ekleyin.

## Sonraki adımlar

<CardGroup cols={2}>
  <Card title="Developer Hub" href="/tr/developers/developer-hub/overview">
    Power BI için bir hizmet hesabı ve API anahtarı oluşturun.
  </Card>

  <Card title="API hızlı başlangıç" href="/tr/developers/v1/quickstart">
    `/v1` isteklerinin kimliğini Basic kimlik doğrulamasıyla doğrulayın.
  </Card>

  <Card title="Sayfalama" href="/tr/developers/v1/pagination">
    Liste zarfı, imleç belirteçleri ve bir tarama boyunca filtre kararlılığı.
  </Card>

  <Card title="Hız sınırları" href="/tr/developers/v1/rate-limits">
    Anahtar başına ve IP başına bütçeler, 429 sözleşmesi ve yeniden deneme yönergeleri.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.