חלונות זמן גישה

Access Time Windows מאפשרים לחבר שמגיע משותף (Partner Member) לשאת גישה זמנית או חוזרת שסופקה על ידי מערכת חיצונית. דף זה הוא המדריך המוסמך ליצרנים עבור access_time_windows ו-access_time_windows_revision.

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

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

שדות תמונת המצב של החבר

  • access_time_windows array אופציונלי

    תמונת המצב המלאה של Access Time Windows עבור החבר ומקור השותף הזה. תמונת מצב יכולה לשלב חלונות absolute ו-weekly. השמיטו את השדה כדי להשאיר את תמונת המצב השמורה ללא שינוי; שלחו [] כדי לשלול כל חלון ששייך למקור הזה.

  • access_time_windows_revision integer חובה עם access_time_windows

    מספר שלם אי-שלילי, בטוח לשימוש ב-JSON, שעולה באופן מונוטוני בזרם הגרסאות של החבר/המקור הזה. הוא חובה בכל פעם ש-access_time_windows קיים, ואסור שיעלה על 9007199254740991.

כל תמונת מצב מקבלת 50 חלונות לכל היותר. external_id, scope_key ו-label מקבלים 120 תווים לכל היותר. חלונות מוחלטים יכולים להשתרע על 90 ימים לכל היותר. יום שבועי מקבל שמונה מקטעים לא חופפים לכל היותר. נתונים לא תקינים נדחים, ולא נקטעים או מנוחשים.

מבני החלונות

חלון מוחלט מזהה מרווח מדויק אחד:

{
  "external_id": "access-4b1782",
  "kind": "absolute",
  "from": 1792053000,
  "to": 1792058400,
  "timezone": "Australia/Perth",
  "scope_key": "after_hours_entry",
  "label": "Temporary site access"
}

שדות החובה הם external_id, kind, from ו-to. timezone, scope_key ו-label הם אופציונליים.

חלון שבועי מזהה מרווחים חוזרים לפי שעון הקיר המקומי, ובאופן אופציונלי מוגבל בנקודות זמן של UTC:

{
  "external_id": "weekly-early-access",
  "kind": "weekly",
  "timezone": "America/New_York",
  "access_hours": {
    "sun": [],
    "mon": [["06:00", "08:30"]],
    "tue": [["06:00", "08:30"]],
    "wed": [["06:00", "08:30"]],
    "thu": [["06:00", "08:30"]],
    "fri": [["06:00", "08:30"]],
    "sat": []
  },
  "active_from": 1790812800,
  "active_until": 1796083200,
  "scope_key": "main_entry",
  "label": "Weekday early access"
}

שדות החובה הם external_id, kind ו-access_hours. timezone, active_from, active_until, scope_key ו-label הם אופציונליים.

בקשה מלאה משולבת

בקשה זו מחליפה את תמונת המצב הקודמת של החבר, ששייכת למקור, בחלון מוחלט אחד ובחלון שבועי אחד:

curl -X POST https://partner-api.performancehub.co/partner/v1/members \
  -u sk_live_YOUR_PARTNER_SECRET_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "members": [
      {
        "member_id": "member_11111",
        "facility_id": "550e8400-e29b-41d4-a716-446655440000",
        "first_name": "John",
        "last_name": "Smith",
        "access_time_windows_revision": 1842,
        "access_time_windows": [
          {
            "external_id": "access-4b1782",
            "kind": "absolute",
            "from": 1792053000,
            "to": 1792058400,
            "timezone": "Australia/Perth",
            "scope_key": "after_hours_entry",
            "label": "Temporary site access"
          },
          {
            "external_id": "weekly-early-access",
            "kind": "weekly",
            "timezone": "America/New_York",
            "access_hours": {
              "sun": [],
              "mon": [["06:00", "08:30"]],
              "tue": [["06:00", "08:30"]],
              "wed": [["06:00", "08:30"]],
              "thu": [["06:00", "08:30"]],
              "fri": [["06:00", "08:30"]],
              "sat": []
            },
            "active_from": 1790812800,
            "active_until": 1796083200,
            "scope_key": "main_entry",
            "label": "Weekday early access"
          }
        ]
      }
    ]
  }'

השתמשו ב-facility_id או ב-linking_identifier בהתאם לשיטת הקישור שהגדרתם. אל תשלחו את שניהם.

כללי זמן וגבולות

from, to, active_from ו-active_until הם מספרים שלמים של שניות Unix ב-JSON, שמזהים נקודות זמן של UTC:

  • שלחו מספר שלם של JSON ללא מירכאות. מחרוזות מספריות, מספרי נקודה צפה, מחרוזות RFC 3339 ואלפיות שנייה של Unix נדחים.
  • כל מרווח הוא חצי-פתוח: [from, to). הגישה יכולה להתחיל בדיוק ב-from; היא כבר אינה פעילה בדיוק ב-to.
  • from חייב להיות קטן מ-to. כששני הגבולות השבועיים קיימים, active_from חייב להיות קטן מ-active_until.
  • אל תגזרו ערכי epoch מתאריך/שעה מקומיים ללא אזור זמן. תחילה המירו את זמן המקור לנקודת זמן של UTC, לאחר מכן חלקו את אלפיות השנייה של Unix ב-1000 וודאו שהתוצאה היא מספר שלם.

המרת epoch

2026-10-15T08:30:00Z מומר ל-1792053000:

UTC instant                 Unix milliseconds   Unix seconds sent
2026-10-15T08:30:00Z        1792053000000        1792053000

המרה בלתי תלויה בשפת תכנות:

instant = parse_source_timestamp_with_explicit_zone(source_timestamp)
milliseconds = instant.to_unix_milliseconds()
assert milliseconds modulo 1000 equals 0
seconds = milliseconds / 1000
assert seconds is a JSON-safe nonnegative integer

אם למקור יש דיוק של פחות משנייה, הגדירו מדיניות עיגול מכוונת להתחלה ולסיום לפני בדיקות האינטגרציה. לעולם אל תעבירו אלפיות שנייה ותסתמכו על כך שה-API יסיק את היחידה.

אזורי זמן ושעון קיץ

timezone חייב להיות אזור זמן IANA תקין, כגון UTC, Australia/Perth או America/New_York. ברירת המחדל היא UTC כשהוא מושמט.

  • חלונות מוחלטים: from ו-to כבר מזהים נקודות זמן מדויקות של UTC. timezone הוא מטא-נתונים לתצוגה ולביקורת בלבד; שינוי שלו לעולם אינו מזיז אף אחת מנקודות הזמן.
  • חלונות שבועיים: timezone מגדיר את שעון הקיר המקומי ואת כללי שעון הקיץ שמשמשים להערכת access_hours. אל תמירו מראש מקטעים שבועיים ל-UTC.
  • במעבר לשעון קיץ, שעה מקומית שאזור הזמן מדלג עליה לעולם אינה מתרחשת.
  • במעבר משעון קיץ, שתי נקודות הזמן האמיתיות שבהן חוזרת אותה שעה מקומית תואמות למקטע השבועי.
  • active_from ו-active_until נשארים נקודות זמן של UTC גם כשחלון שבועי משתמש באזור זמן שאינו UTC.

access_hours משתמש בערכים באותיות קטנות מ-sun עד sat. כל ערך הוא מערך של צמדי [start, end] בפורמט HH:mm של 24 שעות, עם אפסים מובילים. המקטעים חייבים להיות לא ריקים, מסודרים ולא חופפים. end אינו כלול. מקטע אינו יכול לחצות את חצות; פצלו מרווח לילי מהמקור למקטע מאוחר ביום הראשון ולמקטע מוקדם ביום שלמחרת.

מזהים יציבים ומיפוי טווחים

external_id חייב להיות יציב לאורך כל חיי החלון הלוגי, וייחודי בתוך תמונת המצב של החבר. שמרו עליו יציב כשהשעות או התוויות משתנות, כדי שמפעילים ורשומות ביקורת יוכלו להצליב בין גרסאות. אל תשתמשו בו שוב עבור חלון שאינו קשור.

scope_key הוא יעד הגישה הלוגי והיציב שלכם. ברירת המחדל שלו היא default. מנהל מתקן ממפה כל מפתח מותר ליעדים ב-Performance Hub: כל המתקן, דלת או טווח גישה:

Partner scope_key           Performance Hub mapping
default                     Facility's configured default target
main_entry                  Access Scope "Main entrances"
after_hours_entry           Doors approved for after-hours entry

שותפים אינם שולחים בתוך חלון מזהים של מתקן, דלת או טווח גישה של Performance Hub. scope_key לא מוכר, מושבת או לא ממופה גורם לכך שהחלון הזה אינו מעניק גישה; הוא אינו מרחיב את הגישה. סכמו על המיפויים ובדקו אותם לפני ההשקה.

תמונות מצב מלאות וגרסאות

התייחסו לשני השדות כאל מחיצה עם ניהול גרסאות נפרד בתוך פעולת ה-upsert של החבר:

קלטתוצאה
access_time_windows מושמטתמונת המצב הקיימת נשארת ללא שינוי; אין צורך בגרסה
קיים עם גרסה חדשה יותרכל תמונת המצב השמורה של החבר/המקור הזה מוחלפת
אותה גרסה ואותו מטען מנורמלשליחה חוזרת אידמפוטנטית; אין שינוי בתמונת המצב
אותה גרסה ומטען מנורמל שונההתנגשות; הקצו גרסה חדשה לאחר ביצוע התאמה
גרסה ישנה יותרתמונת המצב הישנה מדולגת; היא אינה יכולה לדרוס מצב חדש יותר
access_time_windows: [] עם גרסה חדשה יותרשולל במפורש את כל החלונות ששייכים למקור

תמונת מצב היא שלמה, ולא תיקון חלקי. אם בגרסה 1842 יש חלונות A ו-B, אז גרסה 1843 שמכילה רק את B מסירה את A.

נהלו זרם גרסאות מתואם אחד לכל שותף/חבר, בין תהליכי webhook, תשאול, שליחה חוזרת והתאמה מלאה. עדיף להשתמש ברצף עמיד במסד נתונים. אפשר להשתמש בערך של אלפיות שנייה של Unix כגרסה רק כשכל הכותבים מתואמים ומבטיחים סדר וייחודיות מוחלטים; זהו ערך גרסה, ולא שדה epoch.

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

ביטול, פקיעת תוקף וכשלים במערכת המקור

  • ביטול חלון אחד: שלפו בהצלחה את המצב המוסמך מהמקור, הסירו את החלון הזה ושלחו את תמונת המצב המלאה שנותרה עם גרסה חדשה יותר.
  • ביטול כל החלונות: שלחו access_time_windows: [] עם גרסה חדשה יותר.
  • מתן אפשרות לחלון מוחלט לפוג: הוא מפסיק להתיר גישה ב-to; שלחו התאמה מאוחרת יותר ללא הרשומה שפג תוקפה כדי לשמור על תמונות מצב נקיות.
  • סיום חלון שבועי: הסירו אותו בתמונת מצב חדשה יותר, או השתמשו ב-active_until כשלמקור יש נקודת סיום ידועה.
  • השליפה מהמקור נכשלת או אינה שלמה: אל תשלחו [] ואל תפרסמו תמונת מצב חלקית. שלחו התראה ונסו שוב את השליפה. הפיכת כשל במערכת המקור לתמונת מצב ריקה היא שלילת גישה לא מכוונת.
  • בקשת Partner API נכשלת באימות: תקנו את הנתונים ושלחו גרסה חדשה יותר רק אם תמונת המצב המנורמלת משתנה. נסו שוב כשלים חולפים עם הגרסה המקורית.

עדכניות המקור מוגדרת לכל אינטגרציה לפי קצב המסירה וההתאמה שהיא התחייבה אליו. לפני ההפעלה, סכמו על הגיל המרבי של נתוני המקור, על מה שקורה כשהוא נחצה, על ספי ההתראה ועל האפשרות של מפעילים לאשר הסתמכות רק על הגבולות של כל חלון. חלון ממקור לא עדכני לעולם אינו הופך לכלל שלילה; כללים רגילים והרשאות קבועות נשארים נתיבי גיבוי.

אלגוריתם היצרן

השתמשו בתהליך העבודה הזה, הבלתי תלוי בשפת תכנות, בכל עדכון של חבר:

function publish_member(member_key):
    source_result = fetch_authoritative_access_state(member_key)

    if source_result.failed or source_result.incomplete:
        record_failure(member_key)
        schedule_retry(member_key)
        return without sending access_time_windows

    windows = []
    for source_window in source_result.current_windows:
        if source_window.is_absolute:
            windows.append({
                external_id: stable_window_id(source_window),
                kind: "absolute",
                from: to_integer_unix_seconds(source_window.start_instant),
                to: to_integer_unix_seconds(source_window.end_instant),
                timezone: source_window.display_timezone or "UTC",
                scope_key: stable_scope_key(source_window) or "default",
                label: safe_label(source_window)
            })
        else:
            windows.append({
                external_id: stable_window_id(source_window),
                kind: "weekly",
                timezone: valid_iana_zone(source_window.timezone) or "UTC",
                access_hours: split_validate_and_sort_weekly_blocks(source_window),
                active_from: optional_integer_unix_seconds(source_window.active_start),
                active_until: optional_integer_unix_seconds(source_window.active_end),
                scope_key: stable_scope_key(source_window) or "default",
                label: safe_label(source_window)
            })

    normalized_snapshot = validate_sort_and_deduplicate(windows)
    revision = next_durable_member_revision(member_key)
    request = member_upsert_with(normalized_snapshot, revision)
    persist_for_retry(member_key, request)

    response = post_request(request)
    if response.is_transient_or_unknown:
        retry_exact_request_with_same_revision(request)
    else if response.is_revision_conflict:
        reconcile_with_latest_committed_source_state(member_key)
    else if response.is_stale_skip:
        stop_replaying_this_revision_and_reconcile(member_key)

רשימת בדיקה להסמכה

השלימו את הבדיקות האלה במתקן stage באמצעות מיפוי ה-scope_key הסופי:

  • חלון מוחלט מתקבל כשאזור הזמן מושמט, ומוערך כ-UTC.
  • חלון מוחלט עם אזור זמן IANA מותאם אישית שומר על אותן נקודות זמן של from/to.
  • חלון שבועי עם אזור זמן IANA מותאם אישית עוקב אחר שעון הקיר המקומי במעברים לשעון קיץ וממנו.
  • תמונת מצב משולבת של חלונות מוחלטים/שבועיים מחליפה את תמונת המצב הקודמת לחלוטין.
  • הגישה מתחילה בדיוק ב-from ומסתיימת בדיוק ב-to; גם סופי המקטעים השבועיים אינם כלולים.
  • עדכון שעות תחת אותו external_id עם גרסה חדשה יותר נכנס לתוקף.
  • השמטת access_time_windows משנה שדות אחרים של החבר בלי לשנות את החלונות.
  • שליחה חוזרת של אותה גרסה/אותו מטען היא אידמפוטנטית.
  • אותה גרסה/מטען שונה מחזירה התנגשות ואינה משנה את תמונת המצב.
  • גרסה ישנה יותר שמגיעה שלא לפי הסדר מדווחת כלא עדכנית ואינה יכולה להחזיר את המצב לאחור.
  • [] חדש יותר שולל את החלונות ששייכים למקור; כשל בשליפה ממערכת המקור אינו שולח דבר.
  • scope_key לא מוכר או לא ממופה אינו מעניק גישה, ויוצר אזהרה שגלויה למפעילים.
  • התראות על גיל נתוני המקור ומדיניות המקור הלא עדכני שהוגדרה פועלות בסף שסוכם.
  • נתיבי ה-webhook וההתאמה המלאה מתכנסים לאותה תמונת מצב ולאותו סדר גרסאות.
  • המסירה בזרם מגיעה ל-Door & Gate Access בתוך היעד שסוכם.
  • חלון פעיל יכול להתיר גישה לדלת הפיזית הממופה, אבל אינו יכול לעקוף השבתת משתמש, blockAllAccess, תחזוקה או גידור גאוגרפי.
  • לפני חלון, מחוצה לו ואחריו, כללים רגילים והרשאות קבועות עדיין מתירים גישה למשתמשים שעומדים בתנאים בדרך אחרת.

וקטורי בדיקה מינימליים

[
  {
    "name": "absolute-half-open",
    "window": {
      "external_id": "tv-absolute",
      "kind": "absolute",
      "from": 1792053000,
      "to": 1792058400
    },
    "expect": {
      "1792052999": "inactive",
      "1792053000": "active",
      "1792058399": "active",
      "1792058400": "inactive"
    }
  },
  {
    "name": "weekly-dst",
    "window": {
      "external_id": "tv-weekly-dst",
      "kind": "weekly",
      "timezone": "America/New_York",
      "access_hours": {
        "sun": [["01:00", "03:30"]],
        "mon": [],
        "tue": [],
        "wed": [],
        "thu": [],
        "fri": [],
        "sat": []
      }
    },
    "expect": "Evaluate local wall time: skipped times do not occur; both repeated 01:30 instants qualify."
  }
]

שלחו גם וקטורים שליליים עבור אלפיות שנייה של Unix, מחרוזות מספריות, מספרי נקודה צפה, אזורי IANA לא תקינים, from >= to, external_id כפול, מקטעים חופפים, מקטעים ליליים, יותר משמונה מקטעים ביום, יותר מ-50 חלונות ומפתחות טווח לא ממופים.