> ## 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 بواجهة Living Security Public API باستخدام Power Query، وابنِ نموذجًا بمخطط نجمي على /v1، واختر مجموعات البيانات التشغيلية أو مجموعات بيانات إعداد التقارير للتصيّد الاحتيالي.

هذا الدليل مثال متكامل لقراءة `/v1` في Power BI. ويغطي
نمط الاتصال عبر Power Query (M)، ونموذجًا دلاليًا بمخطط نجمي مبنيًا
على `/v1/activities`، والتحديث التزايدي، ومتى تُستخدم مجموعات بيانات
**إعداد التقارير** المعاينة لنتائج التصيّد الاحتيالي لكل شخص.

تستخدم الأمثلة مضيف الإنتاج في الولايات المتحدة
(`https://api.us-east-1.livingsecurity.ai/v1`). بالنسبة إلى مؤسسة في الاتحاد الأوروبي، استخدم
`https://api.eu-west-1.livingsecurity.ai/v1` في كلٍّ من `Fn_FetchAll` و
`Fn_GetJson`، ثم اربط بيانات اعتماد مصدر البيانات بذلك المضيف. راجع
[المناطق](/ar/developers/v1/regions).

يستخدم هذا الدليل بيانات اعتماد Basic المخزّنة مع مفتاح API. ولا تحتاج إلى
عميل OAuth. راجع [المصادقة](/ar/developers/v1/authentication) لمعرفة خيارات
بيانات الاعتماد، و[Developer Hub](/ar/developers/developer-hub/overview)
للاطلاع على الوصول والإعداد.

## الغرض من هذا المثال

استخدمه عندما تحتاج إلى:

* تحميل نقاط نهاية القوائم في `/v1` إلى مجموعة بيانات Power BI مع ترقيم صفحات صحيح
  قائم على المؤشر وإعادة محاولة عند تجاوز حد المعدل.
* بناء نموذج دلالي تكون فيه المقاييس ("الكيانات النشطة"، "الأنشطة لكل
  كيان نشط") بُعدية بدلًا من عدّها من جدول مسطّح.
* تقليص تحديث Power BI المجدول من قراءة كاملة للسجل التاريخي إلى
  نافذة يومية صغيرة باستخدام التحديث التزايدي.
* اختيار مستوى التفصيل المناسب للتصيّد الاحتيالي: إجماليات الحملات (تشغيلية) مقابل
  النتائج لكل شخص والأحداث المصنّفة (معاينة إعداد التقارير).

## المتطلبات الأساسية

* مفتاح API يملك الأذونات التي تحتاج إليها استعلاماتك:
  * المثال التشغيلي: `activity:read` و`entity:read` و
    `simulation:read`.
  * التصيّد الاحتيالي لكل شخص عبر مجموعات بيانات إعداد التقارير: `reporting:read`.
    راجع [مفاتيح API](/ar/developers/developer-hub/api-keys).
* Power BI Desktop، أو مساحة عمل Power BI / Fabric يمكنك فيها إنشاء نموذج
  دلالي وجدولة التحديث.

## اختيار مستوى التفصيل المناسب

يوفّر `/v1` سطحين مترابطين للتصيّد الاحتيالي. وهما غير قابلين للاستبدال.

| الحاجة | الاستخدام | الإذن | مستوى التفصيل |
| - | - | - | - |
| إجماليات مسار الحملة | `GET /v1/simulations/{id}/results` | `simulation:read` | صف واحد لكل محاكاة (تم التسليم، تم الفتح، تم النقر، …) |
| النتائج لكل شخص (تشغيلية) | `GET /v1/simulation_results` | `simulation:read` | صف واحد لكل كيان × محاكاة |
| النتائج لكل شخص (ذكاء الأعمال / إعداد التقارير) | `GET /v1/reporting/simulation_results` | `reporting:read` | مستوى التفصيل نفسه، وبأسماء حقول ذكاء الأعمال (`entity_id`، `simulation_id`)، مع علامة مائية `updated_after` |
| وقائع التفاعل المصنّفة | `GET /v1/reporting/simulation_events` | `reporting:read` | واقعة واحدة لكل تفاعل (`simulation.link_clicked`، …)، `occurred_after` |
| بُعد القوى العاملة الحالي | `GET /v1/reporting/entities` | `reporting:read` | صف حالي واحد لكل كيان. اربط `reporting.simulation_results.entity_id = reporting.entities.id` |
| إصدارات الكيانات الحالية | `GET /v1/reporting/entity_versions` | `reporting:read` | فترة حالية واحدة مفتوحة النهاية لكل كيان. لا يوجد جدول لسجل الإصدارات |
| عضويات المجموعة الحالية | `GET /v1/reporting/cohort_memberships` | `reporting:read` | واقعة حالية واحدة لكل `(cohort_id, entity_id)`. لا يوجد جدول لسجل العضويات |
| محور الربط لجميع أنواع الأنشطة | `GET /v1/activities` | `activity:read` | حدث واحد لكل نشاط، مع `participants[]` متداخل |

تستدعي أمثلة استعلامات Power BI أدناه المسارات **التشغيلية**
(`/v1/simulations`، `/v1/simulations/{id}/results`، `/v1/activities`،
`/v1/entities`). وبالنسبة إلى إعداد تقارير التصيّد الاحتيالي لكل شخص، يُفضّل استخدام عائلة
**إعداد التقارير** المعاينة الواردة أدناه. لا يمكن لمفتاح مُنح `reporting:read` فقط
استدعاء `/v1/simulation_results` التشغيلي (تعيد واجهة API الرمز `403`).

### مقياس الصعوبة

صعوبة المحاكاة عدد صحيح من 1 إلى 5 في `/v1/simulations` (`difficulty_level`)،
و`/v1/simulation_results` (`difficulty_level`)، وخصائص `/v1/activities`
(`difficulty_tier`)، و`/v1/reporting/simulation_events` (`difficulty_tier`).
`1` هي الأسهل و`5` هي الأصعب. لا تملك المحاكاات التكيفية والتدريجية
قيمة واحدة على مستوى الحملة، لذا تكون `/v1/simulations.difficulty_level` هي `null`
بينما يمكن أن تظل `difficulty_level` لكل مستلم في النتائج محددة. وللمطابقة مع
صادرات منخفض/متوسط/مرتفع القديمة، اعتبر `1–2` منخفضة، و`3` متوسطة، و`4–5`
مرتفعة.

<Warning>
  لا تبنِ تكاملات ذكاء أعمال جديدة على استدعاءات شبكة غير موثقة في لوحة التحكم
  أو على النظرة العامة القديمة المنسّقة لواجهة `/api/v1` Public API. صادِق على مضيف
  `/v1` الإقليمي للمؤسسة باستخدام مفتاح API من Developer Hub. راجع
  [المناطق](/ar/developers/v1/regions).
</Warning>

## نمط الاتصال (Power Query)

تستند كل استعلامات المثال إلى أربع قواعد. وتفرض خدمة
Power BI كلًّا منها، وليست مجرد تفضيلات أسلوبية.

<AccordionGroup>
  <Accordion title="أبقِ عنوان URL الأساسي في Web.Contents ثابتًا وأنهِه عند /v1">
    ضع كل ما هو ديناميكي في `RelativePath` و`Query`. يجب أن يكون الوسيط الأول في
    `Web.Contents` سلسلة نصية حرفية تنتهي عند جذر `/v1`
    الإقليمي للمؤسسة:

    * الولايات المتحدة: `https://api.us-east-1.livingsecurity.ai/v1`
    * الاتحاد الأوروبي: `https://api.eu-west-1.livingsecurity.ai/v1`

    ثم يمرّر المستدعون المورد وحده (`"simulations"` وليس
    `"v1/simulations"`). لا تلصق مضيف الولايات المتحدة لمؤسسة في الاتحاد الأوروبي.

    إذا بُني الوسيط الأول بدمج السلاسل النصية، فلن تتمكن خدمة Power BI
    من تحديد مصدر البيانات بشكل ثابت، وسيفشل التحديث المجدول بخطأ
    "مصدر بيانات ديناميكي"، حتى لو عمل الاستعلام في Power BI
    Desktop.
  </Accordion>

  <Accordion title="وفّر مفتاح API كبيانات اعتماد Basic مخزّنة، وليس في نص الاستعلام">
    عندما يطلب Power BI بيانات الاعتماد لمصدر بيانات `/v1`، اختر
    **Basic** والصق مفتاح API في حقل **اسم المستخدم**. وضع قيمة بديلة مثل
    `x` في حقل **كلمة المرور**. ترفض خدمة Power BI كلمة المرور الفارغة
    حتى لو تجاهلت واجهة API هذا الحقل. ويعمل أيضًا وضع المفتاح في كلمة المرور مع
    اسم مستخدم بديل.

    لا تضف ترويسة `Authorization` في M. فهي تعمل على جهازك المكتبي، ثم
    تدمج المفتاح في ملف الاستعلام وتتعارض مع التحديث المجدول.
  </Accordion>

  <Accordion title="دع اختبار الاتصال في مربع حوار بيانات الاعتماد يعمل">
    `GET /v1` يتطلب مصادقة: فهو يعيد `200` مع مفتاح صالح و`401`
    بدونه. وجّه مصدر البيانات إلى `…/v1` و**لا** تتخطَّ الاختبار —
    فالمفتاح الصالح يُحفظ، أما المفتاح الملغى أو المشوّه فيفشل فورًا.

    كانت الأمثلة الأقدم تستخدم المضيف المجرد كمصدر بيانات وتطلب منك تخطي
    الاختبار لأن `GET /` تعيد دائمًا `404`. أعد توجيه تلك النماذج إلى `…/v1`.
  </Accordion>

  <Accordion title="عيّن limit=500 صراحةً في كل استعلام قائمة">
    حجم الصفحة الافتراضي في كل نقطة نهاية قائمة في `/v1` هو 25. ويؤدي إبقاؤه
    على الوضع الافتراضي إلى مضاعفة عدد الطلبات للبيانات نفسها. يجب أن تكون قيم الاستعلام نصية —
    `limit = "500"` في M، وليس `limit = 500`.
  </Accordion>
</AccordionGroup>

### دالة ترقيم الصفحات المشتركة

يستدعي كل استعلام في المثال `Fn_FetchAll` واحدة تمرّ على
`next_page_token` إلى أن يصبح `null` (راجع
[ترقيم الصفحات](/ar/developers/v1/pagination)). وفيما يلي خصائصها البارزة:

* **عنوان URL أساسي ثابت لـ `/v1`، و`RelativePath` و`Query` ديناميكيان.** مطلوب
  للتحديث المجدول ولاختبار بيانات الاعتماد.
* **`ManualStatusHandling = {429}` مع إعادة محاولة محدودة.** تقرأ الدالة
  `Retry-After` عندما يتيحها Power Query، وإلا تنتظر 30 ثانية،
  وتتوقف بعد 5 استجابات `429` متتالية مع خطأ صريح بدلًا من
  الدوران حتى تنتهي مهلة التحديث. ولا تُعاد محاولة `5xx` عمدًا —
  إذ ينبغي أن يظهر خطأ الخادم، لا أن ينتج بصمت مجموعة بيانات جزئية.
* **`Timeout` مدتها 60 ثانية.** تستجيب الصفحات عادةً في أقل بكثير من ثانية،
  لذا فإن دقيقة من الصمت تعني أن الطلب قد توقف.
* **`Binary.Buffer` قبل `Json.Document`.** فمن دون التخزين المؤقت، قد يعيد محرك
  mashup طلب تدفق الاستجابة إذا قرأه أكثر من مرة.
* **يُمرَّر `baseQuery` دون تغيير في كل تكرار.** رموز الصفحات
  هي مؤشرات معتمة وموقّعة مرتبطة بالمؤسسة وبيانات الاعتماد وإصدار
  العقد وبصمة المرشحات والفرز. ويُرفض تغيير مرشح
  أثناء القراءة باعتباره رمزًا غير صالح بدلًا من إعادة البدء من الصفحة
  الأولى.

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

### استعلامات مصنّفة لكل نقطة نهاية

يتبع كل مستهلك لـ `Fn_FetchAll` الشكل نفسه: استدعاء نقطة النهاية،
وإسقاط قائمة أعمدة ثابتة، وحذف المميِّز `object`، وتصنيف
الطوابع الزمنية بالنوع `datetimezone` حتى لا تنزاح مقاييس التاريخ بصمت بسبب
المنطقة الزمنية لسعة التحديث.

<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` على استقرار المخطط عندما
  تعيد نقطة النهاية صفر صفوف. أما `Table.FromRecords({})` المجردة فتنتج
  جدولًا بلا أعمدة وتفشل كل الخطوات اللاحقة بسبب الحقول المفقودة.
</Tip>

## Excel

يعمل Power Query (M) نفسه في Get & Transform في Excel — فهو المحرك نفسه
الموجود في Power BI Desktop. استخدم هذا عندما تريد جدولًا في ورقة عمل
بدلًا من نموذج دلالي.

في Excel لنظام Windows (Microsoft 365 أو Excel 2024 أو 2021 أو 2019 أو 2016):

1. **Data → Get Data → From Other Sources → Blank Query.**
2. في Power Query Editor، اختر **الرئيسية → محرر متقدم**. الصق
   `Fn_FetchAll` أولًا، ثم أعد تسمية ذلك الاستعلام إلى `Fn_FetchAll` (Query
   Settings → Name، أو انقر بزر الماوس الأيمن في جزء Queries → **Rename**).
3. كرّر ذلك مع `Fn_GetJson`، ثم مع كل استعلام تقرير.
4. يفتح **Home → Close & Load → Close & Load To…** مربع الحوار **Import Data**.
   اختر **Only Create Connection** لـ `Fn_FetchAll` و
   `Fn_GetJson` (فهما دالتان وليستا جدولين). واختر **Table** لكل
   استعلام تقرير تريده في ورقة عمل.
5. عند أول تحديث، اختر **Basic**: مفتاح API في **User name**، وقيمة
   بديلة مثل `x` في **Password**. يخزّن Excel بيانات اعتماد واحدة
   لكل عنوان URL لمصدر بيانات؛ وتعيد الاستعلامات اللاحقة التي تصل إلى مضيف `/v1` نفسه
   استخدامها.

<Note>
  قد يكون **Close & Load To…** معطّلًا عند إنشاء أول استعلام في
  مصنف. استخدم **Close & Load**، ثم في Excel اختر **Data →
  Queries & Connections**، وانقر بزر الماوس الأيمن على الاستعلام، واختر **Load To**.
  وقد وثّقت Microsoft هذا المسار لهذه الحالة.
</Note>

لا يمكنك إرسال `Living-Security-Version` من Power Query على مصدر
مصادَق عليه — وهو القيد نفسه في Power BI. ثبّت حساب الخدمة أو مرّر `api_version` في سجل
الاستعلام. راجع
[إصدارات العقد وبيانات الاعتماد المخزّنة](#إصدارات-العقد-وبيانات-الاعتماد-المخزّنة).

استعلامات تقارير جاهزة (تعيينات الأعمدة والاختلافات عن تصدير
مزوّد سابق):

<CardGroup cols={3}>
  <Card title="الامتثال لكل شخص" href="/ar/developers/v1/reports/per-person-compliance">
    تسجيلات التدريب على مستوى الشخص × التكليف.
  </Card>

  <Card title="سجل التصيّد الاحتيالي عبر البريد الإلكتروني" href="/ar/developers/v1/reports/email-phishing-log">
    تفاعلات التصيّد الاحتيالي على مستوى الحدث، خلال آخر 12 شهرًا.
  </Card>

  <Card title="المستخدمون الأكثر عرضة للاختراق" href="/ar/developers/v1/reports/most-susceptible-users">
    ملخص لكل شخص خلال 12 شهرًا للنقرات وبيانات الاعتماد والإبلاغ.
  </Card>
</CardGroup>

## مجموعات بيانات إعداد التقارير المعاينة (التصيّد الاحتيالي لكل شخص)

توفّر عائلة إعداد التقارير المعاينة مجموعات بيانات للتصيّد الاحتيالي لكل شخص.
وتتطلب `reporting:read` وصولًا إلى إعداد التقارير لمؤسستك.
استخدم كتالوج مجموعات البيانات أدناه لفحص مخططاتها وسياسات بياناتها.

اكتشف الكتالوج الفعلي:

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

تحدد كل مجموعة بيانات مستوى التفصيل والمفتاح الأساسي والصلات والعلامة المائية وسياسة التصحيح
والحذف.

### simulation\_results

* **المسار:** `GET /v1/reporting/simulation_results`
* **مستوى التفصيل:** كيان × محاكاة
* **المرشحات:** `simulation` و`entity` و`updated_after`، بالإضافة إلى `limit` /
  `page_token`
* **كائن الإرسال:** `reporting.simulation_result`
* **مفاتيح الربط:** `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
```

استخدم `updated_after` كحد أدنى شامل للعلامة المائية عند تحديث الأقسام
الحديثة. تُعاد صياغة أبعاد النتائج القابلة للتغيير في مكانها تحت `id` نفسه.

### simulation\_events

* **المسار:** `GET /v1/reporting/simulation_events`
* **مستوى التفصيل:** واقعة مصنّفة واحدة لكل تفاعل مرصود
* **المرشحات:** `simulation` و`entity` و`event_type` و`occurred_after`، بالإضافة إلى
  `limit` / `page_token`
* **كائن الإرسال:** `reporting.simulation_event`
* **أنواع الأحداث:** رموز عامة مغلقة مثل `simulation.delivered`
  و`simulation.opened` و`simulation.link_clicked`
  و`simulation.credentials_submitted` و`simulation.reported` — وليست أنواع أنشطة
  ClickHouse `phishing.simulation.*` الخام
* **الجهاز:** `device_type` هو الفئة المشتقة (تشمل الرموز المرصودة
  `desktop` و`mobile` و`tablet` و`unknown`). `user_agent` الخام غير موجود على
  `/v1`. ولا يوجد عنوان IP الخام أبدًا على `/v1` (لا يُحفظ أبدًا؛ `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
```

استخدم `occurred_after` لعمليات تحميل الأحداث التزايدية. الوقائع للإلحاق فقط؛
والوصولات المتأخرة تكون صفوفًا جديدة.

### entities

* **المسار:** `GET /v1/reporting/entities`
* **مستوى التفصيل:** صف حالي واحد لكل كيان
* **المرشحات:** `type` و`updated_after`، بالإضافة إلى `limit` / `page_token`
* **كائن الإرسال:** `reporting.entity`
* **مفتاح الربط:** `id` / `entity_id` (القيمة نفسها)

اربط وقائع التصيّد الاحتيالي بالأشخاص باستخدام
`reporting.simulation_results.entity_id = reporting.entities.id`.
يبقى `GET /v1/entities` التشغيلي على `entity:read` وهو إذن مختلف.

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

تتضمن مجموعتا البيانات هاتان استثناءً مسمًّى يقتصر على الوضع الحالي:

> لا يوجد جدول موثوق لسجل الإصدارات أو العضويات. تعرض مجموعة البيانات هذه الصف الحالي كفترة واحدة مفتوحة النهاية (`valid_to` فارغة، و`is_current` صحيحة). `valid_from` هي `created_at` أو `updated_at` بخلاف ذلك لإصدارات الكيانات، ووقت رصد الطلب لعضويات المجموعة. لا تُختلق فترات تاريخية مغلقة.

* **entity\_versions:** `GET /v1/reporting/entity_versions`. المرشحات نفسها لـ
  entities. `valid_from` هي `created_at` أو `updated_at` بخلاف ذلك. ويُستبعد الكيان
  الذي يكون كلا طابعيه الزمنيين فارغًا.
* **cohort\_memberships:** `GET /v1/reporting/cohort_memberships`. المرشحات:
  `cohort` و`entity` و`updated_after`. `updated_at` هو آخر تحديث للمجموعة
  حتى يبقى التحديث التزايدي دقيقًا. `valid_from` هو وقت
  رصد الطلب. اربط `entity_id` بـ `reporting.entities.id`.

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

`/v1/activities` هو تدفق الأحداث ذات الطوابع الزمنية للمؤسسة. وليس له
مقاييس رقمية — فكل كمية في التقرير هي عدد صفوف — مما يجعل المخطط
النجمي ملائمًا بطبيعته.

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

* **`Activities`** هو جدول وقائع الأحداث بمستوى التفصيل الحقيقي. صف واحد لكل نشاط.
  تُحذف المشاركات هنا وتُعاد بناؤها كجدول مستقل.
* **`ActivityParticipants`** موجود لأن المشاركين
  علاقة واحد إلى متعدد فعلًا، بأدوار مثل `actor` و`target`. إن التسطيح على
  `Activities` سيؤدي إما إلى تكرار صفوف الأنشطة (مما يضخّم كل عدّ)
  أو إلى إسقاط المشاركين الإضافيين.
  والعلاقة مع `Activities` هي العلاقة الوحيدة ثنائية الاتجاه في النموذج،
  بحيث تصل شريحة الكيان إلى جدول الوقائع.
* **`Entities`** هو البُعد الذي يسمّي المشاركين. تحمل صفوف المشاركين
  في النشاط `entity_id` ودورًا لكن لا تحمل اسم عرض.
* **`Dates`** يُنشأ في M (دون أي استدعاء لواجهة API) ويُعلَّم كجدول
  التاريخ في النموذج. وهذا ما يجعل التحليلات الزمنية في DAX
  (`DATESINPERIOD`، `DATEADD`) مشروعة. وتعلن أعمدة اسم الشهر واليوم
  `sortByColumn` على نظيراتها الرقمية حتى لا ترتب العناصر المرئية الأشهر
  أبجديًا.

جميع المقاييس صريحة (يعيّن النموذج `discourageImplicitMeasures`): إجمالي
الأنشطة، والكيانات النشطة، والأنشطة لكل كيان نشط، وأيام النشاط،
ومتوسط يومي لآخر 7 أيام.

### شكل جدول الربط

يمرّ `ActivityParticipants` على `/v1/activities` ويفكّك مصفوفة `participants`
إلى صف واحد لكل مشارك:

```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` بشكل مستقل بدلًا من
الإشارة إلى استعلام `Activities`. في Power BI، يجلب كل استعلامين محمَّلين كجدولين
بياناتهما بشكل مستقل وقت التحديث. وإبقاؤهما مستقلين
يتيح للخدمة تحديث أقسام الجدولين بالتوازي.

## التحديث التزايدي

يعلن كلا الجدولين على مستوى الوقائع (`Activities` و`ActivityParticipants`)
سياسة تحديث تزايدي في Power BI: الاحتفاظ بخمس سنوات من الأقسام السنوية،
وإعادة استيراد آخر 14 يومًا عند كل تحديث.

الآلية هي زوج من معاملات DateTime، `RangeStart` و`RangeEnd`،
تعيد خدمة Power BI ربطهما لكل قسم وقت التحديث. ويدمج كل استعلام وقائع هذه المعاملات في مرشحَي
`start_date` و`end_date` في واجهة API، بحيث يتناسب حجم الطلبات على مستوى القسم مع القسم،
وليس مع السجل التاريخي الكامل. ويوسّع الاستعلام نافذة API بدقيقة
من كل جانب ويعيد تطبيق حد `[RangeStart, RangeEnd)` نصف المفتوح الدقيق
محليًا، بحيث لا تعتمد صحة الأقسام على ما إذا كان الخادم
يعامل حدوده كشاملة أم حصرية.

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

يطبّق أول تحديث بعد النشر السياسة: تحميل تاريخي كامل،
وهو بطيء. وأي تحديث بعد ذلك يمس النافذة التزايدية فقط.

القياس على مجموعة بيانات تضم نحو 158,000 نشاط ونحو 2,300 كيان:

| التحديث | الزمن الفعلي |
| - | - |
| الأول (تطبيق السياسة، تحميل تاريخي كامل) | \~31 دقيقة |
| كل تحديث بعد ذلك | **\~15 ثانية** |

<Warning>
  تتجاهل نقطة نهاية REST للتحديث الكلاسيكي في Power BI (`POST .../refreshes` مع
  `notifyOption` فقط) سياسات التحديث بصمت وتجري قراءة كاملة في كل
  مرة. ولتطبيق السياسة عبر REST API، استخدم التحديث المحسّن: أرسل
  `type` و`commitMode` و`applyRefreshPolicy` في المتن. أما التحديثات
  التي يطلقها مجدوِل خدمة Power BI فتطبّق السياسات دائمًا — هذه
  الخاصية مقتصرة على استدعاء REST.
</Warning>

<Note>
  نافذة الـ 14 يومًا سخية عمدًا. فاستيعاب الأنشطة ليس للإلحاق فقط عند
  اللحظة الحالية: إذ تضيف عمليات الملء الرجعي للموصّلات أحداثًا بطوابع زمنية ماضية.
  ونافذة من يوم إلى يومين ستُفوّت هذه الأحداث بصورة دائمة.
</Note>

## توسيع نافذة التحديث داخل واجهة API

يقبل `GET /v1/activities` المرشحين `start_date` و`end_date` اللذين تُدمج فيهما
معاملات Power BI، ويرشّح `event_ts` بين الحدين.
`Entities` هو بُعد، ولذلك يحدّثه هذا المثال بالكامل في كل مرة
بدلًا من التحديث التزايدي.

تستخدم مجموعات بيانات إعداد التقارير علاماتها المائية الخاصة (`updated_after`،
`occurred_after`) بدلًا من نافذة تاريخ الأنشطة.

## حدود المعدل أثناء التحديث

يفرض `/v1` ميزانيات منفصلة لكل مفتاح ولكل عنوان IP (راجع
[حدود المعدل](/ar/developers/v1/rate-limits)). التزم بـ `429` + `Retry-After`؛ ولا
تضمّن ميزانية طلبات ثابتة في نموذجك. وهناك خاصيتان في تحديثات Power BI
لهما أهمية:

* **القراءة الكاملة لجدول واحد تتم تسلسليًا.** يحتاج كل طلب صفحة إلى رمز
  الصفحة السابقة، لذا يكون الجدول الواحد محكومًا بزمن الاستجابة بغض النظر عن حجم
  الصفحة.
* **تحديثات الجداول المتزامنة تتضاعف.** إن تحديث عدة جداول كبيرة
  بالتوازي هو الحالة التي تبلغ السقف، ولهذا يهم مسار إعادة محاولة 429
  في `Fn_FetchAll`. فضّل عددًا أقل من الجداول المتوازية بدلًا من
  افتراض تزامن غير محدود.

جرى اختبار إعادة المحاولة مقابل محدِّد معدل مشبع فعليًا. فقد اكتمل تحديث
جرى أثناء إبقاء حمل عمل موازٍ المفتاحَ عند سقفه عمدًا،
وأنتج أعداد الصفوف نفسها كخط أساس دون تنافس —
إذ أعادت المحاولات صفحات كاملة دون فجوات.

## إصدارات العقد وبيانات الاعتماد المخزّنة

يقبل `/v1` ترويسة الطلب `Living-Security-Version` لتثبيت إصدار عقد منشور
(راجع [البدء السريع لواجهة API](/ar/developers/v1/quickstart)). لا يستطيع Power
Query إرسال تلك الترويسة عندما يستخدم مصدر البيانات بيانات اعتماد مخزّنة —
إذ تسمح مصادر البيانات المجهولة فقط بترويسات مخصصة. ويؤدي إضافتها إلى فشل التحديث
بالخطأ:

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

أبقِ المفتاح في بيانات الاعتماد المخزّنة في Power BI واحذف الترويسة المخصصة.
وعند عدم تحديد إصدار صريح في الطلب، تستخدم واجهة API `contract_version` المثبّت لحساب الخدمة،
أو أحدث إصدار منشور إذا لم يكن هناك تثبيت.
أكّد إصدار عقد الحساب مع مسؤول API لديك قبل
نشر التحديث. راجع [سجل تغييرات API](/ar/developers/v1/changelog) للاطلاع على
الإصدارات المدعومة.

إذا بدأ التحديث يتصرف بشكل غير متوقع، فافحص ترويسة استجابة API
`Living-Security-Version` لمعرفة أي عقد
خدم الطلب فعليًا.

## تحديث النموذج والتحقق منه

انشر النموذج الدلالي إلى مساحة عمل Power BI، واربط بيانات اعتماده المخزّنة
بمصدر `/v1` الإقليمي، ثم شغّل تحديثًا قبل تفعيل
الجدولة. راجع سجل التحديث بحثًا عن حالات الفشل أو القراءات غير المكتملة.

تحقق من أعداد الصفوف مقابل قراءة مستقلة من API باستخدام المرشحات نفسها.
يتضمن [دليل ترقيم الصفحات](/ar/developers/v1/pagination) أمثلة Python وcURL
لقراءة مجموعة نتائج كاملة. قد يبدو التحديث الذي يقرأ الصفحة الأولى فقط
ناجحًا على مجموعة بيانات صغيرة؛ لذا قارن الأعداد قبل
الاعتماد على التقرير.

## خيارات النموذج

* **لإجماليات الحملات والنتائج لكل شخص مستويا تفصيل مختلفان.**
  يستدعي `SimulationResults` `GET /v1/simulations/{id}/results`
  (طلب GET واحد لكل تشغيل). وبالنسبة إلى التصيّد الاحتيالي لكل شخص، أضف استعلامات لـ
  `GET /v1/reporting/simulation_results` و
  `GET /v1/reporting/simulation_events` (أو `GET /v1/simulation_results`
  التشغيلي إذا كنت تملك `simulation:read` بالفعل).
* **`vectors` في `/v1/simulations` مسطّح إلى سلسلة نصية مفصولة بفواصل.**
  ولا يمكن تقطيع عمود من نوع قائمة كبُعد حقيقي. إذا احتاجت صفحة تقرير
  إلى الترشيح حسب المتجه، فأضف جدول ربط بين المحاكاة والمتجه.

## الخطوات التالية

<CardGroup cols={2}>
  <Card title="Developer Hub" href="/ar/developers/developer-hub/overview">
    أنشئ حساب خدمة ومفتاح API لـ Power BI.
  </Card>

  <Card title="البدء السريع لواجهة API" href="/ar/developers/v1/quickstart">
    صادِق على طلبات `/v1` باستخدام مصادقة Basic.
  </Card>

  <Card title="ترقيم الصفحات" href="/ar/developers/v1/pagination">
    غلاف القائمة، ورموز المؤشر، وثبات المرشحات عبر القراءة.
  </Card>

  <Card title="حدود المعدل" href="/ar/developers/v1/rate-limits">
    ميزانيات كل مفتاح وكل عنوان IP، وعقد 429، وإرشادات إعادة المحاولة.
  </Card>
</CardGroup>


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