> ## 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 Query के माध्यम से Power BI को Living Security Public API से कनेक्ट करें, /v1 पर star-schema मॉडल बनाएं, और phishing के लिए operational तथा reporting datasets में से चुनें।

यह गाइड `/v1` को Power BI में पढ़ने का एक end-to-end उदाहरण है। इसमें
Power Query (M) कनेक्टिविटी पैटर्न, `/v1/activities` पर बना star-schema semantic मॉडल, incremental refresh, और प्रति-व्यक्ति phishing परिणामों के लिए **reporting**
preview datasets का उपयोग कब करना है, यह शामिल है।

उदाहरणों में US production host
(`https://api.us-east-1.livingsecurity.ai/v1`) का उपयोग किया गया है। EU संगठन के लिए, `Fn_FetchAll` और
`Fn_GetJson` दोनों में `https://api.eu-west-1.livingsecurity.ai/v1` का उपयोग करें, फिर data-source credential को उसी host से बाइंड करें। देखें
[Regions](/hi/developers/v1/regions)।

यह गाइड API key के साथ संग्रहीत Basic credentials का उपयोग करती है। आपको
OAuth क्लाइंट की आवश्यकता नहीं है। credential विकल्पों के लिए [Authentication](/hi/developers/v1/authentication) देखें, और एक्सेस तथा सेटअप के लिए [Developer Hub](/hi/developers/developer-hub/overview) देखें।

## यह उदाहरण किसलिए है

इसका उपयोग तब करें जब आपको निम्नलिखित करना हो:

* `/v1` list endpoints को सही cursor pagination और rate-limit retry के साथ Power BI dataset में लोड करना।
* ऐसा semantic मॉडल बनाना जिसमें measures ("active entities", "activities per
  active entity") किसी flat table से गिनने के बजाय dimensional हों।
* निर्धारित Power BI refresh को incremental refresh की सहायता से पूरे इतिहास के full walk से घटाकर छोटी दैनिक विंडो तक लाना।
* phishing के लिए सही grain चुनना: campaign totals (operational) बनाम
  प्रति-व्यक्ति परिणाम और typed events (reporting preview)।

## पूर्वापेक्षाएं

* एक API key, जिसमें आपकी queries के लिए आवश्यक permissions हों:
  * Operational उदाहरण: `activity:read`, `entity:read`, और
    `simulation:read`।
  * reporting datasets के माध्यम से प्रति-व्यक्ति phishing: `reporting:read`।
    देखें [API keys](/hi/developers/developer-hub/api-keys)।
* Power BI Desktop, या Power BI / Fabric workspace जहां आप semantic मॉडल बना सकें और refresh शेड्यूल कर सकें।

## सही grain चुनें

`/v1` phishing के लिए दो संबंधित surfaces प्रदान करता है। वे एक-दूसरे के स्थान पर उपयोग करने योग्य नहीं हैं।

| आवश्यकता | उपयोग करें | Permission | Grain |
| - | - | - | - |
| Campaign funnel totals | `GET /v1/simulations/{id}/results` | `simulation:read` | प्रति simulation एक पंक्ति (delivered, opened, clicked, …) |
| प्रति-व्यक्ति परिणाम (operational) | `GET /v1/simulation_results` | `simulation:read` | प्रति entity × simulation एक पंक्ति |
| प्रति-व्यक्ति परिणाम (BI / reporting) | `GET /v1/reporting/simulation_results` | `reporting:read` | वही grain, BI फ़ील्ड नाम (`entity_id`, `simulation_id`), `updated_after` watermark |
| Typed interaction facts | `GET /v1/reporting/simulation_events` | `reporting:read` | प्रति interaction एक fact (`simulation.link_clicked`, …), `occurred_after` |
| वर्तमान Workforce dimension | `GET /v1/reporting/entities` | `reporting:read` | प्रति entity एक वर्तमान पंक्ति। `reporting.simulation_results.entity_id = reporting.entities.id` को join करें |
| वर्तमान entity versions | `GET /v1/reporting/entity_versions` | `reporting:read` | प्रति entity एक open-ended वर्तमान अंतराल। कोई version-history table मौजूद नहीं है |
| वर्तमान Cohort memberships | `GET /v1/reporting/cohort_memberships` | `reporting:read` | प्रति `(cohort_id, entity_id)` एक वर्तमान fact। कोई membership-history table मौजूद नहीं है |
| सभी गतिविधि प्रकारों के लिए join spine | `GET /v1/activities` | `activity:read` | प्रति गतिविधि एक event, nested `participants[]` |

नीचे दी गई Power BI उदाहरण queries **operational** routes
(`/v1/simulations`, `/v1/simulations/{id}/results`, `/v1/activities`,
`/v1/entities`) को कॉल करती हैं। प्रति-व्यक्ति phishing reporting के लिए नीचे दिए गए
**reporting** preview family को प्राथमिकता दें। जिस key को केवल `reporting:read` दिया गया है, वह operational `/v1/simulation_results` को कॉल नहीं कर सकती (API `403` लौटाता है)।

### Difficulty scale

Simulation difficulty एक 1–5 पूर्णांक है, जो `/v1/simulations` (`difficulty_level`),
`/v1/simulation_results` (`difficulty_level`), `/v1/activities` properties
(`difficulty_tier`), और `/v1/reporting/simulation_events` (`difficulty_tier`) पर होता है।
`1` सबसे आसान और `5` सबसे कठिन है। Adaptive और progressive simulations का कोई एकल campaign-level मान नहीं होता, इसलिए `/v1/simulations.difficulty_level` का मान `null` होता है,
जबकि परिणामों पर प्रति-प्राप्तकर्ता `difficulty_level` फिर भी सेट हो सकता है। पुराने low/medium/high exports से मैप करने के लिए, `1–2` को low, `3` को medium, और `4–5` को
high मानें।

<Warning>
  नए BI integrations को अनडॉक्यूमेंटेड Dashboard network calls या पुराने curated `/api/v1` Public API overview के आधार पर न बनाएं। संगठन के क्षेत्रीय `/v1` host पर Developer Hub API key से authenticate करें। देखें
  [Regions](/hi/developers/v1/regions)।
</Warning>

## कनेक्टिविटी पैटर्न (Power Query)

उदाहरण की प्रत्येक query चार नियमों पर आधारित है। इनमें से प्रत्येक को Power BI Service लागू करता है, यह शैली की पसंद का विषय नहीं है।

<AccordionGroup>
  <Accordion title="Web.Contents का base URL स्थिर रखें और उसे /v1 पर समाप्त करें">
    सभी गतिशील तत्वों को `RelativePath` और `Query` में रखें। `Web.Contents` का पहला argument एक literal string होना चाहिए जो संगठन के क्षेत्रीय `/v1` root पर समाप्त हो:

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

    इसके बाद कॉलर केवल resource पास करते हैं (`"simulations"`, न कि
    `"v1/simulations"`)। EU संगठन के लिए US host न चिपकाएं।

    यदि पहला argument string concatenation से बनाया गया है, तो Power BI Service data source को स्थिर रूप से पहचान नहीं सकती और निर्धारित refresh "dynamic data source" त्रुटि के साथ विफल हो जाता है, भले ही query Power BI Desktop में काम करती हो।
  </Accordion>

  <Accordion title="API key को संग्रहीत Basic credential के रूप में दें, query टेक्स्ट में नहीं">
    जब Power BI `/v1` data source पर credentials मांगे, तो
    **Basic** चुनें और API key को **username** के रूप में पेस्ट करें। **password** फ़ील्ड में `x` जैसा कोई placeholder डालें। Power BI Service खाली password अस्वीकार कर देती है, भले ही API उस फ़ील्ड को अनदेखा करता हो। placeholder username के साथ key को password के रूप में देना भी काम करता है।

    M में `Authorization` header न जोड़ें। यह आपके डेस्कटॉप पर काम करता है, फिर key को query फ़ाइल में ही सम्मिलित कर देता है और निर्धारित refresh में बाधा डालता है।
  </Accordion>

  <Accordion title="credential dialog की test connection को चलने दें">
    `GET /v1` authenticated है: वैध key के साथ यह `200` लौटाता है और key के बिना `401`।
    data source को `…/v1` पर इंगित करें और test को **न** छोड़ें —
    सही key सेव हो जाती है, जबकि रद्द की गई या गलत प्रारूप वाली key तुरंत विफल हो जाती है।

    पुराने उदाहरणों में data source के रूप में केवल host का उपयोग किया जाता था और test छोड़ने को कहा जाता था क्योंकि `GET /` हमेशा `404` होता है। उन मॉडलों को फिर से `…/v1` पर इंगित करें।
  </Accordion>

  <Accordion title="प्रत्येक list query पर limit=500 स्पष्ट रूप से सेट करें">
    प्रत्येक `/v1` list endpoint पर डिफ़ॉल्ट page size 25 है। इसे डिफ़ॉल्ट पर छोड़ने से उसी डेटा के लिए request की संख्या कई गुना बढ़ जाती है। Query मान टेक्स्ट होने चाहिए —
    M में `limit = "500"`, न कि `limit = 500`।
  </Accordion>
</AccordionGroup>

### साझा pagination function

उदाहरण की प्रत्येक query एक ही `Fn_FetchAll` को कॉल करती है, जो `next_page_token` तब तक walk करता है जब तक वह `null` न हो जाए (देखें
[pagination](/hi/developers/v1/pagination))। इसकी उल्लेखनीय विशेषताएं:

* **स्थिर `/v1` base URL, गतिशील `RelativePath` और `Query`।** निर्धारित refresh और credential test के लिए आवश्यक।
* **सीमित retry के साथ `ManualStatusHandling = {429}`।** यह function Power Query द्वारा उपलब्ध कराए जाने पर `Retry-After` पढ़ता है, अन्यथा 30 सेकंड प्रतीक्षा करता है, और लगातार 5 `429` responses के बाद refresh के timeout होने तक लूप करने के बजाय स्पष्ट त्रुटि के साथ रुक जाता है। `5xx` को जानबूझकर retry नहीं किया जाता — server त्रुटि सामने आनी चाहिए, चुपचाप आंशिक dataset नहीं बनना चाहिए।
* **60-सेकंड `Timeout`।** Pages आमतौर पर एक सेकंड से बहुत कम में उत्तर देते हैं, इसलिए एक मिनट की चुप्पी का अर्थ है कि request विफल हो चुकी है।
* **`Json.Document` से पहले `Binary.Buffer`।** बफ़रिंग के बिना, यदि mashup engine response stream को एक से अधिक बार पढ़े तो वह उसे फिर से request कर सकता है।
* **`baseQuery` प्रत्येक iteration में अपरिवर्तित पास किया जाता है।** Page tokens अपारदर्शी, हस्ताक्षरित cursors हैं जो संगठन, credential, contract version, और filters तथा sort के fingerprint से बंधे होते हैं। walk के बीच में filter बदलने पर उसे पहले page से दोबारा शुरू करने के बजाय अमान्य token के रूप में अस्वीकार कर दिया जाता है।

```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
```

### प्रति endpoint typed queries

`Fn_FetchAll` का प्रत्येक उपभोक्ता एक ही ढांचे का पालन करता है: endpoint को कॉल करें,
स्तंभों की एक निश्चित सूची चुनें, `object` discriminator हटाएं, और
timestamps को `datetimezone` के रूप में टाइप करें ताकि date measures refresh capacity के timezone के कारण चुपचाप न खिसकें।

<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` पर स्पष्ट स्तंभ सूची देने से schema तब भी स्थिर रहता है जब कोई endpoint शून्य पंक्तियां लौटाता है। केवल `Table.FromRecords({})` देने पर शून्य-स्तंभ वाली table मिलती है और बाद के सभी चरण अनुपलब्ध फ़ील्ड्स पर विफल हो जाते हैं।
</Tip>

## Excel

वही Power Query (M) Excel के Get & Transform में चलता है — यह Power BI Desktop जैसा ही इंजन है। इसका उपयोग तब करें जब आपको semantic मॉडल के बजाय वर्कशीट table चाहिए।

Windows के लिए Excel पर (Microsoft 365, Excel 2024, 2021, 2019, या 2016):

1. **Data → Get Data → From Other Sources → Blank Query।**
2. Power Query Editor में, **Home → Advanced Editor**। पहले
   `Fn_FetchAll` पेस्ट करें, फिर उस query का नाम बदलकर `Fn_FetchAll` करें (Query
   Settings → Name, या Queries pane में राइट-क्लिक करके **Rename**)।
3. `Fn_GetJson` के लिए यही दोहराएं, फिर प्रत्येक report query के लिए।
4. **Home → Close & Load → Close & Load To…** से **Import Data**
   dialog खुलता है। `Fn_FetchAll` और
   `Fn_GetJson` के लिए **Only Create Connection** चुनें (ये functions हैं, tables नहीं)। जिन report queries को वर्कशीट पर चाहते हैं उनके लिए **Table** चुनें।
5. पहले refresh पर **Basic** चुनें: API key को **User name** के रूप में, और `x` जैसा कोई placeholder **Password** के रूप में दें। Excel प्रति data source URL एक credential संग्रहीत करता है; उसी `/v1` host तक पहुंचने वाली बाद की queries उसी का पुन: उपयोग करती हैं।

<Note>
  किसी वर्कबुक में पहली बार query बनाते समय **Close & Load To…** धूसर (dimmed) हो सकता है। **Close & Load** का उपयोग करें, फिर Excel में **Data →
  Queries & Connections** चुनें, query पर राइट-क्लिक करें, और **Load To** चुनें।
  इस स्थिति के लिए यही मार्ग Microsoft द्वारा प्रलेखित है।
</Note>

आप authenticated स्रोत पर Power Query से `Living-Security-Version` नहीं भेज सकते — Power BI में भी यही प्रतिबंध है। service account को pin करें या query record में `api_version` पास करें। देखें
[Contract versions और संग्रहीत credentials](#contract-versions-और-संग्रहीत-credentials)।

तैयार report queries (स्तंभ मैपिंग और पिछले प्रदाता के export से अंतर):

<CardGroup cols={3}>
  <Card title="Per-person compliance" href="/hi/developers/v1/reports/per-person-compliance">
    व्यक्ति × असाइनमेंट grain पर Training नामांकन।
  </Card>

  <Card title="Email phishing log" href="/hi/developers/v1/reports/email-phishing-log">
    Event-स्तर की phishing interactions, पिछले 12 महीने।
  </Card>

  <Card title="Most susceptible users" href="/hi/developers/v1/reports/most-susceptible-users">
    प्रति-व्यक्ति 12-महीने का click, credential, और report rollup।
  </Card>
</CardGroup>

## Reporting preview datasets (प्रति-व्यक्ति phishing)

Reporting preview family प्रति-व्यक्ति phishing के लिए datasets प्रदान करता है।
इसके लिए `reporting:read` और आपके संगठन के लिए reporting एक्सेस आवश्यक है।
इसके schemas और data policies देखने के लिए नीचे दिए गए dataset catalog का उपयोग करें।

लाइव catalog खोजें:

```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
```

प्रत्येक dataset अपना grain, primary key, joins, watermark, correction, और deletion policy घोषित करता है।

### simulation\_results

* **Path:** `GET /v1/reporting/simulation_results`
* **Grain:** entity × simulation
* **Filters:** `simulation`, `entity`, `updated_after`, साथ ही `limit` /
  `page_token`
* **Wire object:** `reporting.simulation_result`
* **Join keys:** `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
```

हाल के partitions को refresh करते समय `updated_after` को inclusive निचले watermark के रूप में उपयोग करें। परिवर्तनशील result dimensions उसी `id` के अंतर्गत यथास्थान पुनः लिखी जाती हैं।

### simulation\_events

* **Path:** `GET /v1/reporting/simulation_events`
* **Grain:** प्रत्येक देखी गई interaction के लिए एक typed fact
* **Filters:** `simulation`, `entity`, `event_type`, `occurred_after`, साथ ही
  `limit` / `page_token`
* **Wire object:** `reporting.simulation_event`
* **Event types:** बंद सार्वजनिक tokens जैसे `simulation.delivered`,
  `simulation.opened`, `simulation.link_clicked`,
  `simulation.credentials_submitted`, `simulation.reported` — raw
  ClickHouse `phishing.simulation.*` activity types नहीं
* **Device:** `device_type` व्युत्पन्न class है (देखे गए tokens में
  `desktop`, `mobile`, `tablet`, `unknown` शामिल हैं)। Raw `user_agent` `/v1` पर नहीं है। Raw IP कभी `/v1` पर नहीं होता (कभी संग्रहीत नहीं किया जाता; केवल salted `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
```

Incremental event loads के लिए `occurred_after` का उपयोग करें। Facts केवल जोड़े जाते हैं (append-only);
देर से आने वाले डेटा नई पंक्तियों के रूप में आते हैं।

### entities

* **Path:** `GET /v1/reporting/entities`
* **Grain:** प्रति entity एक वर्तमान पंक्ति
* **Filters:** `type`, `updated_after`, साथ ही `limit` / `page_token`
* **Wire object:** `reporting.entity`
* **Join key:** `id` / `entity_id` (समान मान)

Phishing facts को लोगों से join करने के लिए
`reporting.simulation_results.entity_id = reporting.entities.id` का उपयोग करें।
Operational `GET /v1/entities` `entity:read` पर ही रहता है और यह एक अलग permission है।

```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 और cohort\_memberships

ये दोनों datasets एक नामित केवल-वर्तमान अपवाद के साथ आते हैं:

> कोई प्रामाणिक version या membership-history table मौजूद नहीं है। यह dataset वर्तमान पंक्ति को एक open-ended अंतराल के रूप में प्रस्तुत करता है (`valid_to` null है, `is_current` true है)। entity versions के लिए `valid_from` का मान `created_at` होता है, अन्यथा `updated_at`, और cohort memberships के लिए request का observation समय। बंद ऐतिहासिक अंतराल गढ़े नहीं जाते।

* **entity\_versions:** `GET /v1/reporting/entity_versions`। entities जैसे ही filters। `valid_from` का मान `created_at` होता है, अन्यथा `updated_at`। जिस entity के दोनों timestamps null हों, उसे शामिल नहीं किया जाता।
* **cohort\_memberships:** `GET /v1/reporting/cohort_memberships`। Filters:
  `cohort`, `entity`, `updated_after`। `updated_at` Cohort का अंतिम अपडेट है
  ताकि incremental refresh सटीक रहे। `valid_from` request का
  observation समय है। `entity_id` को `reporting.entities.id` से join करें।

```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 पर Star-schema semantic मॉडल

`/v1/activities` संगठन की समय-चिह्नित event stream है। इसमें कोई संख्यात्मक measures नहीं हैं — किसी रिपोर्ट में प्रत्येक मात्रा पंक्तियों की गिनती होती है — जिससे star schema स्वाभाविक रूप से उपयुक्त बैठता है।

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

* **`Activities`** वास्तविक grain पर event fact है। प्रति गतिविधि एक पंक्ति।
  Participants यहां हटा दिए जाते हैं और अलग table के रूप में पुनर्निर्मित किए जाते हैं।
* **`ActivityParticipants`** इसलिए मौजूद है क्योंकि participants वास्तव में one-to-many होते हैं, जिनकी भूमिकाएं `actor` और `target` जैसी होती हैं। `Activities` पर flatten करने से या तो गतिविधि पंक्तियां डुप्लिकेट हो जातीं (हर गिनती बढ़ जाती) या अतिरिक्त participants छूट जाते।
  `Activities` से संबंध मॉडल का एकमात्र bidirectional संबंध है, ताकि entity slicer fact table तक पहुंच सके।
* **`Entities`** वह dimension है जो participants को नाम देती है। किसी गतिविधि की participant पंक्तियों में `entity_id` और भूमिका होती है, लेकिन display name नहीं।
* **`Dates`** M में जनरेट की जाती है (शून्य API calls) और मॉडल की date table के रूप में चिह्नित की जाती है। इसी से DAX time intelligence
  (`DATESINPERIOD`, `DATEADD`) मान्य होता है। महीने और सप्ताह के दिन के नाम वाले स्तंभ अपने संख्यात्मक समकक्षों पर `sortByColumn` घोषित करते हैं ताकि visuals महीनों को वर्णानुक्रम में क्रमबद्ध न करें।

सभी measures स्पष्ट (explicit) हैं (मॉडल `discourageImplicitMeasures` सेट करता है): कुल
गतिविधियां, active entities, प्रति active entity गतिविधियां, गतिविधि वाले दिन,
और 7-दिवसीय trailing दैनिक औसत।

### Bridge table का स्वरूप

`ActivityParticipants` `/v1/activities` को walk करता है और `participants` array को प्रति participant एक पंक्ति में विस्तारित करता है:

```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` को स्वतंत्र रूप से walk करता है, `Activities` query का संदर्भ लेने के बजाय। Power BI में, दो tables के रूप में लोड की गई दो queries प्रत्येक refresh के समय स्वतंत्र रूप से डेटा प्राप्त करती हैं। उन्हें स्वतंत्र रखने से service दोनों tables के partitions को समानांतर रूप से refresh कर पाती है।

## Incremental refresh

दोनों fact-grain tables (`Activities` और `ActivityParticipants`) Power BI incremental refresh policy घोषित करती हैं: वार्षिक partitions के 5 वर्ष रखें,
प्रत्येक refresh पर पिछले 14 दिन पुनः import करें।

यह तंत्र DateTime parameters की एक जोड़ी, `RangeStart` और `RangeEnd`, पर आधारित है, जिन्हें Power BI service refresh के समय प्रत्येक partition के लिए पुनः बाइंड करती है। प्रत्येक fact query इन parameters को API के `start_date` और `end_date` filters में मिला देती है, इसलिए partition-स्तर पर request की मात्रा partition के अनुपात में होती है, कुल इतिहास के नहीं। Query API विंडो को प्रत्येक ओर एक मिनट चौड़ा करती है और सटीक half-open `[RangeStart, RangeEnd)` सीमा को स्थानीय रूप से पुनः लागू करती है, इसलिए partition की शुद्धता इस पर निर्भर नहीं करती कि server अपनी सीमाओं को inclusive मानता है या exclusive।

```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
```

Deploy के बाद पहला refresh policy लागू करता है: पूर्ण ऐतिहासिक लोड, जो धीमा होता है। इसके बाद का प्रत्येक refresh केवल incremental विंडो को छूता है।

लगभग 158,000 गतिविधियों और लगभग 2,300 entities वाले dataset पर मापा गया:

| Refresh | Wall time |
| - | - |
| पहला (policy लागू करना, पूर्ण ऐतिहासिक लोड) | \~31 min |
| इसके बाद का प्रत्येक refresh | **\~15 s** |

<Warning>
  Power BI का classic refresh REST endpoint (केवल
  `notifyOption` के साथ `POST .../refreshes`) refresh policies को चुपचाप अनदेखा करता है और हर बार पूरा walk करता है। REST API के माध्यम से policy लागू करने के लिए enhanced refresh का उपयोग करें: body में `type`, `commitMode`, और `applyRefreshPolicy` भेजें। Power BI Service scheduler द्वारा ट्रिगर किए गए refresh हमेशा policies लागू करते हैं — यह विचित्रता केवल REST कॉल की है।
</Warning>

<Note>
  14-दिवसीय विंडो जानबूझकर उदार रखी गई है। गतिविधि ingestion केवल "अभी" पर append-only नहीं है: connector backfills पिछले timestamps वाले events लाते हैं। 1-2 दिन की विंडो उन्हें स्थायी रूप से चूक जाएगी।
</Note>

## Refresh विंडो को API में विस्तृत करना

`GET /v1/activities` वे `start_date` और `end_date` filters स्वीकार करता है जिनमें Power BI parameters मिलते हैं, और सीमाओं के बीच `event_ts` को फ़िल्टर करता है।
`Entities` एक dimension है, इसलिए यह उदाहरण इसे हर बार incremental के बजाय पूरी तरह refresh करता है।

Reporting datasets गतिविधि date विंडो के बजाय अपने स्वयं के watermarks (`updated_after`,
`occurred_after`) का उपयोग करते हैं।

## Refresh के दौरान rate limits

`/v1` प्रति-key और प्रति-IP अलग-अलग budgets लागू करता है (देखें
[rate limits](/hi/developers/v1/rate-limits))। `429` + `Retry-After` का पालन करें;
अपने मॉडल में request budget को hard-code न करें। Power BI refreshes की दो विशेषताएं महत्वपूर्ण हैं:

* **एक table का walk क्रमिक होता है।** प्रत्येक page request को पिछले page का token चाहिए, इसलिए page size चाहे जो हो, एक table latency-bound रहती है।
* **समवर्ती table refreshes गुणित होते हैं।** कई बड़ी tables का समानांतर रूप से refresh होना वह स्थिति है जो सीमा तक पहुंचती है, इसी कारण `Fn_FetchAll` में 429 retry पथ महत्वपूर्ण है। असीमित concurrency मानने के बजाय कम समानांतर tables को प्राथमिकता दें।

Retry का परीक्षण वास्तविक रूप से saturated limiter के विरुद्ध किया गया है। जिस refresh के दौरान समानांतर workload ने जानबूझकर key को उसकी सीमा पर रखा, वह फिर भी पूरा हुआ और उसने बिना contention वाले baseline के समान पंक्ति संख्या उत्पन्न की — retries ने पूर्ण pages लौटाए, अंतराल नहीं।

## Contract versions और संग्रहीत credentials

`/v1` प्रकाशित contract version को pin करने के लिए `Living-Security-Version` request header स्वीकार करता है (देखें [API quickstart](/hi/developers/v1/quickstart))। जब data source संग्रहीत credentials का उपयोग करता है, तब Power Query वह header नहीं भेज सकता — केवल anonymous data sources ही custom headers की अनुमति देते हैं। इसे जोड़ने पर refresh इस त्रुटि के साथ विफल होता है:

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

Key को Power BI के संग्रहीत credentials में रखें और custom header को छोड़ दें।
request में स्पष्ट version न होने पर, API service account का pinned `contract_version` उपयोग करता है, या यदि कोई pin सेट नहीं है तो नवीनतम प्रकाशित version।
refresh प्रकाशित करने से पहले अपने API administrator से खाते के contract version की पुष्टि करें। समर्थित versions के लिए [API changelog](/hi/developers/v1/changelog) देखें।

यदि refresh अप्रत्याशित व्यवहार करने लगे, तो API के
`Living-Security-Version` response header का निरीक्षण करें ताकि पता चले कि request को वास्तव में किस contract ने सेवा दी।

## अपने मॉडल को Refresh और सत्यापित करें

Semantic मॉडल को अपने Power BI workspace में प्रकाशित करें, उसके संग्रहीत credential को क्षेत्रीय `/v1` स्रोत से बाइंड करें, और शेड्यूल सक्षम करने से पहले एक refresh चलाएं। विफलताओं या अधूरी रीडिंग के लिए refresh इतिहास की समीक्षा करें।

उन्हीं filters के साथ स्वतंत्र API रीड के विरुद्ध पंक्ति संख्या की जांच करें।
[pagination गाइड](/hi/developers/v1/pagination) में पूर्ण परिणाम सेट पढ़ने के लिए Python और cURL उदाहरण शामिल हैं। जो refresh केवल पहला page पढ़ता है, वह छोटे dataset पर सफल दिख सकता है; रिपोर्ट पर भरोसा करने से पहले संख्याओं की तुलना करें।

## मॉडल संबंधी विकल्प

* **Campaign totals और प्रति-व्यक्ति परिणामों का grain अलग-अलग होता है।**
  `SimulationResults` `GET /v1/simulations/{id}/results` को कॉल करता है
  (प्रति run एक GET)। प्रति-व्यक्ति phishing के लिए,
  `GET /v1/reporting/simulation_results` और
  `GET /v1/reporting/simulation_events` के लिए queries जोड़ें (या यदि आपके पास पहले से `simulation:read` है तो operational
  `GET /v1/simulation_results`)।
* **`/v1/simulations` पर `vectors` को comma-joined string में flatten किया जाता है।**
  list-typed स्तंभ को वास्तविक dimension के रूप में slice नहीं किया जा सकता। यदि किसी रिपोर्ट पृष्ठ को vector के आधार पर फ़िल्टर करना हो, तो simulation-to-vector bridge table जोड़ें।

## अगले चरण

<CardGroup cols={2}>
  <Card title="Developer Hub" href="/hi/developers/developer-hub/overview">
    Power BI के लिए service account और API key बनाएं।
  </Card>

  <Card title="API quickstart" href="/hi/developers/v1/quickstart">
    Basic auth के साथ `/v1` requests को authenticate करें।
  </Card>

  <Card title="Pagination" href="/hi/developers/v1/pagination">
    List envelope, cursor tokens, और walk के दौरान filter की स्थिरता।
  </Card>

  <Card title="Rate limits" href="/hi/developers/v1/rate-limits">
    प्रति-key और प्रति-IP budgets, 429 contract, और retry मार्गदर्शन।
  </Card>
</CardGroup>


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