> ## 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 אל ה-Public API של Living Security באמצעות Power Query, בנו מודל star-schema על /v1, ובחרו בין מערכי נתונים תפעוליים למערכי נתונים לדיווח עבור פישינג.

מדריך זה הוא דוגמה מקצה לקצה לקריאת `/v1` אל תוך Power BI. הוא מכסה את
תבנית הקישוריות של Power Query (M), מודל סמנטי בסכמת כוכב (star-schema) שנבנה
על `/v1/activities`, רענון מצטבר, ואת המקרים שבהם יש להשתמש במערכי הנתונים
**reporting** בגרסת התצוגה המקדימה עבור תוצאות פישינג ברמת אדם.

הדוגמאות משתמשות במארח הייצור של ארה"ב
(`https://api.us-east-1.livingsecurity.ai/v1`). עבור ארגון באיחוד האירופי, השתמשו ב-
`https://api.eu-west-1.livingsecurity.ai/v1` הן ב-`Fn_FetchAll` והן ב-
`Fn_GetJson`, ולאחר מכן קשרו את פרטי הזיהוי של מקור הנתונים למארח זה. ראו
[אזורים](/he/developers/v1/regions).

מדריך זה משתמש בפרטי זיהוי Basic שמורים עם מפתח API. אין צורך בלקוח
OAuth. ראו [אימות](/he/developers/v1/authentication) לבחירת פרטי הזיהוי,
ו-[Developer Hub](/he/developers/developer-hub/overview)
לגישה והגדרה.

## למה מיועדת דוגמה זו

השתמשו בה כאשר אתם צריכים:

* לטעון נקודות קצה מסוג רשימה של `/v1` אל מערך נתונים של Power BI עם עימוד
  מבוסס סמן (cursor) תקין וניסיון חוזר במקרה של הגבלת קצב.
* לבנות מודל סמנטי שבו המדדים ("ישויות פעילות", "פעילויות לכל ישות
  פעילה") הם ממדיים ולא נספרים מתוך טבלה שטוחה.
* להפחית רענון מתוזמן של Power BI מסריקה מלאה של ההיסטוריה לחלון יומי
  קטן באמצעות רענון מצטבר.
* לבחור את רמת הפירוט הנכונה עבור פישינג: סיכומי קמפיין (תפעולי) לעומת
  תוצאות ברמת אדם ואירועים מוטיפוסים (reporting בגרסת תצוגה מקדימה).

## דרישות מוקדמות

* מפתח API עם ההרשאות הנדרשות לשאילתות שלכם:
  * דוגמה תפעולית: `activity:read`, `entity:read` ו-
    `simulation:read`.
  * פישינג ברמת אדם באמצעות מערכי נתונים לדיווח: `reporting:read`.
    ראו [מפתחות API](/he/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` | שורה אחת לכל ישות × סימולציה |
| תוצאות ברמת אדם (BI / דיווח) | `GET /v1/reporting/simulation_results` | `reporting:read` | אותה רמת פירוט, שמות שדות של BI (`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** בגרסת התצוגה המקדימה שלהלן. מפתח שהוענקה לו רק ההרשאה `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>
  אל תבנו אינטגרציות BI חדשות על קריאות רשת לא מתועדות של לוח מחוונים
  או על סקירת ה-Public API הישנה והמובחרת של `/api/v1`. בצעו אימות מול
  מארח `/v1` האזורי של הארגון באמצעות מפתח API של Developer Hub. ראו
  [אזורים](/he/developers/v1/regions).
</Warning>

## תבנית קישוריות (Power Query)

ארבעה כללים עומדים בבסיס כל שאילתה בדוגמה. כל אחד מהם נאכף על ידי
שירות Power BI ואינו עניין של העדפה סגנונית.

<AccordionGroup>
  <Accordion title="שמרו על כתובת הבסיס של 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
    אינו יכול לזהות סטטית את מקור הנתונים והרענון המתוזמן נכשל עם שגיאת
    "dynamic data source", אף על פי שהשאילתה פועלת ב-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` (ראו
[עימוד](/he/developers/v1/pagination)). המאפיינים הבולטים שלה:

* **כתובת בסיס `/v1` סטטית, ‏`RelativePath` ו-`Query` דינמיים.** נדרש
  לרענון מתוזמן ולבדיקת פרטי הזיהוי.
* **‏`ManualStatusHandling = {429}` עם ניסיון חוזר מוגבל.** הפונקציה קוראת את
  `Retry-After` כאשר Power Query חושף אותו, אחרת ממתינה 30 שניות, ומוותרת
  לאחר 5 תגובות `429` רצופות עם שגיאה מפורשת במקום להמשיך בלולאה עד
  שהרענון נכשל בשל חריגה מזמן. על `5xx` אין ניסיון חוזר במכוון —
  שגיאת שרת צריכה לצוף ולא לייצר בשקט מערך נתונים חלקי.
* **‏`Timeout` של 60 שניות.** עמודים בדרך כלל מגיבים בפחות משנייה, ולכן
  דקה של שתיקה פירושה שהבקשה מתה.
* **‏`Binary.Buffer` לפני `Json.Document`.** ללא חציצה (buffering), מנוע
  ה-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. **בית → Close & Load → Close & Load To…** פותח את תיבת הדו-שיח
   **Import Data**. בחרו **Only Create Connection** עבור `Fn_FetchAll` ו-
   `Fn_GetJson` (אלו פונקציות, לא טבלאות). בחרו **Table** עבור
   כל שאילתת דוח שברצונכם להציג בגיליון.
5. ברענון הראשון, בחרו **Basic**: מפתח ה-API כ-**User name**, מציין מקום כגון
   `x` כ-**סיסמא**. Excel שומר פרטי זיהוי אחד לכל כתובת מקור נתונים;
   שאילתות מאוחרות יותר שפונות לאותו מארח `/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="/he/developers/v1/reports/per-person-compliance">
    רישומי הדרכה ברמת פירוט של אדם × הקצאה.
  </Card>

  <Card title="יומן פישינג בדואר אלקטרוני" href="/he/developers/v1/reports/email-phishing-log">
    אינטראקציות פישינג ברמת אירוע, 12 החודשים האחרונים.
  </Card>

  <Card title="המשתמשים הפגיעים ביותר" href="/he/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`
* **אובייקט wire:** ‏`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`
* **אובייקט wire:** ‏`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` לטעינות אירועים מצטברות. העובדות הן לצירוף בלבד (append-only);
הגעות מאוחרות הן שורות חדשות.

### entities

* **נתיב:** `GET /v1/reporting/entities`
* **רמת פירוט:** שורה נוכחית אחת לכל ישות
* **מסנני:** ‏`type`, ‏`updated_after`, בנוסף ל-`limit` / `page_token`
* **אובייקט wire:** ‏`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` הוא null, ‏`is_current` הוא true). ‏`valid_from` הוא `created_at` ואם לא קיים אז `updated_at` עבור גרסאות ישות, וזמן התצפית של הבקשה עבור חברויות בקבוצות. מרווחים היסטוריים סגורים אינם מפוברקים.

* **entity\_versions:** ‏`GET /v1/reporting/entity_versions`. אותם מסננים כמו ב-
  entities. ‏`valid_from` הוא `created_at` ואם לא קיים אז `updated_at`. ישות
  ששתי חותמות הזמן שלה null מושמטת.
* **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` הוא הקשר הדו-כיווני היחיד במודל,
  ולכן חותך (slicer) של ישות מגיע לטבלת העובדות.
* **‏`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: שמירת 5 שנים של מחיצות שנתיות,
וייבוא מחדש של 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>
  נקודת הקצה הקלאסית של Power BI לרענון ב-REST (‏`POST .../refreshes` עם
  `notifyOption` בלבד) מתעלמת בשקט ממדיניות רענון ומבצעת סריקה מלאה בכל
  פעם. כדי להחיל את המדיניות דרך ה-REST API, השתמשו ברענון משופר (enhanced refresh): שלחו
  את `type`, `commitMode` ו-`applyRefreshPolicy` בגוף הבקשה. רענונים
  שמופעלים על ידי מתזמן שירות Power BI תמיד מחילים מדיניות — תכונה
  מוזרה זו ייחודית לקריאת ה-REST.
</Warning>

<Note>
  חלון 14 הימים נדיב במכוון. קליטת פעילות אינה מסוג
  append-only-at-now: השלמות רטרואקטיביות של מחברים מכניסות אירועים עם חותמות זמן מהעבר.
  חלון של 1-2 ימים היה מפספס אותם לצמיתות.
</Note>

## הרחבת חלון הרענון אל ה-API

‏`GET /v1/activities` מקבל את המסננים `start_date` ו-`end_date` שאליהם
פרמטרי Power BI משתלבים, ומסנן את `event_ts` בין הגבולות.
‏`Entities` הוא ממד, ולכן דוגמה זו מרעננת אותו במלואו בכל פעם
ולא באופן מצטבר.

מערכי נתונים לדיווח משתמשים בסימני המים שלהם (`updated_after`,
`occurred_after`) במקום בחלון התאריכים של הפעילויות.

## הגבלות קצב במהלך רענון

‏`/v1` אוכף תקציבים נפרדים לכל מפתח ולכל IP (ראו
[הגבלות קצב](/he/developers/v1/rate-limits)). כבדו את `429` + `Retry-After`; אל
תקבעו תקציב בקשות בקוד הקשיח של המודל. שני מאפיינים של רענוני Power BI
חשובים:

* **סריקת טבלה אחת היא סדרתית.** כל בקשת עמוד זקוקה לאסימון
  העמוד הקודם, ולכן טבלה אחת מוגבלת על ידי זמן השהיה ללא תלות בגודל
  העמוד.
* **רענוני טבלאות במקביל מכפילים את העומס.** כמה טבלאות גדולות שמתרעננות
  במקביל הן המקרה שפוגע בתקרה, ולכן נתיב הניסיון החוזר ל-429
  ב-`Fn_FetchAll` חשוב. העדיפו פחות טבלאות במקביל על פני
  הנחה של מקביליות בלתי מוגבלת.

הניסיון החוזר נבדק מול מגביל רווי אמיתי. רענון
שרץ בזמן שעומס עבודה מקביל החזיק במכוון את המפתח בתקרה שלו
הושלם בכל זאת והפיק את אותם מספרי שורות כמו קו בסיס ללא תחרות —
הניסיונות החוזרים החזירו עמודים שלמים, ללא פערים.

## גרסאות חוזה ופרטי זיהוי שמורים

‏`/v1` מקבל כותרת בקשה `Living-Security-Version` כדי לנעוץ גרסת חוזה
שפורסמה (ראו [מדריך התחלה מהירה ל-API](/he/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](/he/developers/v1/changelog) לגרסאות
הנתמכות.

אם רענון מתחיל להתנהג באופן בלתי צפוי, בדקו את כותרת התגובה
`Living-Security-Version` של ה-API כדי לראות איזה חוזה
שירת בפועל את הבקשה.

## רענון ואימות המודל שלכם

פרסמו את המודל הסמנטי לסביבת העבודה שלכם ב-Power BI, קשרו את פרטי הזיהוי השמורים שלו
למקור `/v1` האזורי, והריצו רענון לפני הפעלת
תזמון. בדקו את היסטוריית הרענונים לאיתור כשלים או קריאות חלקיות.

בדקו את ספירות השורות מול קריאת API עצמאית עם אותם מסננים.
[מדריך העימוד](/he/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="/he/developers/developer-hub/overview">
    צרו חשבון שירות ומפתח API עבור Power BI.
  </Card>

  <Card title="מדריך התחלה מהירה ל-API" href="/he/developers/v1/quickstart">
    בצעו אימות של בקשות `/v1` באמצעות אימות Basic.
  </Card>

  <Card title="עימוד" href="/he/developers/v1/pagination">
    מעטפת הרשימה, אסימוני סמן ויציבות המסננים לאורך סריקה.
  </Card>

  <Card title="הגבלות קצב" href="/he/developers/v1/rate-limits">
    תקציבים לכל מפתח ולכל IP, חוזה ה-429 והנחיות לניסיון חוזר.
  </Card>
</CardGroup>


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