Finestre temporali di accesso

Le Access Time Windows consentono a un Partner Member di avere un accesso temporaneo o ricorrente fornito da un sistema esterno. Questa pagina è la guida autorevole per i produttori di access_time_windows e access_time_windows_revision.

Le Access Time Windows sono autorizzazioni aggiuntive che possono solo consentire l'accesso. Una finestra attiva può consentire l'accesso, ma una finestra inattiva, scaduta, obsoleta o non mappata non nega un accesso già consentito da una regola ordinaria o da una concessione permanente. Restano comunque validi la disattivazione dell'utente, il blocco esplicito delle porte, la modalità di manutenzione, il geofencing, i limiti di frequenza e la disponibilità del controller.

Le implementazioni per specifici sistemi a monte sono adattatori separati. Devono convertire i propri dati di origine in questo contratto indipendente dal fornitore, senza modificare il significato dei campi.

Campi dell'istantanea del membro

  • access_time_windows array facoltativo

    L'istantanea completa delle Access Time Windows per questo membro e questa fonte partner. Un'istantanea può combinare finestre absolute e weekly. Ometti il campo per lasciare invariata l'istantanea salvata; invia [] per revocare tutte le finestre di proprietà di questa fonte.

  • access_time_windows_revision integer obbligatorio con access_time_windows

    Un intero non negativo, sicuro per JSON e monotonicamente crescente per questo flusso di revisioni membro/fonte. È obbligatorio ogni volta che è presente access_time_windows e non deve superare 9007199254740991.

Ogni istantanea accetta al massimo 50 finestre. external_id, scope_key e label accettano al massimo 120 caratteri. Le finestre assolute possono coprire al massimo 90 giorni. Un giorno settimanale accetta al massimo otto blocchi non sovrapposti. I dati non validi vengono rifiutati, anziché troncati o interpretati per ipotesi.

Forme delle finestre

Una finestra assoluta identifica un unico intervallo esatto:

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

I campi obbligatori sono external_id, kind, from e to. timezone, scope_key e label sono facoltativi.

Una finestra settimanale identifica intervalli ricorrenti in ora locale, eventualmente delimitati da istanti 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"
}

I campi obbligatori sono external_id, kind e access_hours. timezone, active_from, active_until, scope_key e label sono facoltativi.

Richiesta mista completa

Questa richiesta sostituisce la precedente istantanea di proprietà della fonte per il membro con una finestra assoluta e una settimanale:

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"
          }
        ]
      }
    ]
  }'

Usa facility_id oppure linking_identifier in base al metodo di collegamento configurato. Non inviarli entrambi.

Regole su orari e limiti

from, to, active_from e active_until sono secondi Unix espressi come interi JSON che identificano istanti UTC:

  • Invia un intero JSON senza virgolette. Stringhe numeriche, numeri in virgola mobile, stringhe RFC 3339 e millisecondi Unix vengono rifiutati.
  • Ogni intervallo è semiaperto: [from, to). L'accesso può iniziare esattamente a from e non è più attivo esattamente a to.
  • from deve essere minore di to. Quando sono presenti entrambi i limiti settimanali, active_from deve essere minore di active_until.
  • Non ricavare gli epoch da una data/ora locale senza fuso orario. Prima converti l'orario di origine in un istante UTC, poi dividi i millisecondi Unix per 1000 e verifica che il risultato sia un intero.

Conversione in epoch

2026-10-15T08:30:00Z diventa 1792053000:

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

Conversione indipendente dal linguaggio:

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

Se la fonte ha una precisione inferiore al secondo, definisci una regola di arrotondamento intenzionale per inizio e fine prima dei test di integrazione. Non passare mai millisecondi affidandoti all'API per dedurre l'unità.

Fusi orari e ora legale

timezone deve essere un fuso orario IANA valido, come UTC, Australia/Perth o America/New_York. Se omesso, il valore predefinito è UTC.

  • Finestre assolute: from e to identificano già istanti UTC esatti. timezone è solo un metadato di visualizzazione e audit; modificarlo non sposta mai nessuno dei due istanti.
  • Finestre settimanali: timezone definisce l'ora locale e le regole dell'ora legale usate per valutare access_hours. Non convertire in anticipo i blocchi settimanali in UTC.
  • Durante il passaggio all'ora legale, un'ora locale saltata dal fuso orario non si verifica mai.
  • Durante il ritorno all'ora solare, entrambi gli istanti reali che corrispondono all'ora locale ripetuta rientrano nel blocco settimanale.
  • active_from e active_until restano istanti UTC anche quando una finestra settimanale usa un fuso orario diverso da UTC.

access_hours usa le chiavi minuscole da sun a sat. Ogni valore è un array di coppie [start, end] nel formato HH:mm a 24 ore con zeri iniziali. I blocchi devono essere non vuoti, ordinati e non sovrapposti. end è escluso. Un blocco non può attraversare la mezzanotte: suddividi un intervallo notturno di origine in un blocco serale il primo giorno e un blocco mattutino il giorno successivo.

ID stabili e mappatura degli ambiti

external_id deve restare stabile per tutta la durata di una finestra logica ed essere univoco all'interno dell'istantanea del membro. Mantienilo stabile quando cambiano orari o etichette, in modo che operatori e record di audit possano correlare le revisioni. Non riutilizzarlo per una finestra non correlata.

scope_key è la tua destinazione di accesso logica stabile. Il valore predefinito è default. Un amministratore della struttura mappa ogni chiave consentita a destinazioni del Performance Hub valide per l'intera struttura, per una porta o per un Ambito di accesso:

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

I partner non inviano ID di strutture, porte o Ambiti di accesso del Performance Hub all'interno di una finestra. Uno scope_key sconosciuto, disattivato o non mappato nega l'accesso per quella finestra; non amplia l'accesso. Concorda e testa le mappature prima del lancio.

Istantanee complete e revisioni

Tratta i due campi come una partizione con versioni separate all'interno dell'upsert del Member:

InputRisultato
access_time_windows omessoL'istantanea esistente resta invariata; non è richiesta alcuna revisione
Presente con una revisione più recenteL'intera istantanea salvata per questo membro/fonte viene sostituita
Stessa revisione e stesso payload normalizzatoRipetizione idempotente; l'istantanea non cambia
Stessa revisione e payload normalizzato diversoConflitto; assegna una nuova revisione dopo la riconciliazione
Revisione più vecchiaL'istantanea obsoleta viene saltata; non può sovrascrivere uno stato più recente
access_time_windows: [] con una revisione più recenteRevoca esplicitamente tutte le finestre di proprietà della fonte

Un'istantanea è completa, non una modifica parziale. Se la revisione 1842 contiene le finestre A e B, la revisione 1843 che contiene solo B rimuove A.

Mantieni un unico flusso di revisioni coordinato per partner/membro tra worker dei webhook, polling, ripetizioni e riconciliazione completa. È preferibile una sequenza persistente in un database. Un valore in millisecondi Unix può essere usato come revisione solo se tutti i writer si coordinano e garantiscono ordinamento e univocità rigorosi; si tratta di un valore di revisione, non di un campo epoch.

Conserva la richiesta e la revisione esatte finché non conosci la risposta dell'API. Dopo un timeout o un errore temporaneo, riprova con la stessa istantanea normalizzata e la stessa revisione. Non assegnare una nuova revisione solo perché la consegna è incerta.

Annullamento, scadenza ed errori a monte

  • Annullare una finestra: recupera correttamente lo stato autorevole dalla fonte, rimuovi quella finestra e invia l'istantanea completa rimanente con una revisione più recente.
  • Annullare tutte le finestre: invia access_time_windows: [] con una revisione più recente.
  • Lasciare scadere una finestra assoluta: smette di consentire l'accesso a to; invia una riconciliazione successiva senza la voce scaduta per mantenere pulite le istantanee.
  • Terminare una finestra settimanale: rimuovila in un'istantanea più recente oppure usa active_until quando la fonte ha un istante di fine noto.
  • Il recupero dalla fonte non riesce o è incompleto: non inviare [] e non pubblicare un'istantanea parziale. Invia un avviso e riprova il recupero. Trasformare un errore a monte in un'istantanea vuota significa revocare l'accesso involontariamente.
  • La richiesta alla Partner API non supera la convalida: correggi i dati e invia una revisione più recente solo se l'istantanea normalizzata cambia. Riprova gli errori temporanei con la revisione originale.

L'aggiornamento dei dati di origine si configura per ogni integrazione in base alla frequenza di consegna e di riconciliazione promessa. Prima dell'attivazione, concorda l'età massima dei dati di origine, cosa succede quando viene superata, le soglie di avviso e se gli operatori possono accettare di affidarsi solo ai limiti di ciascuna finestra. Una finestra con dati di origine obsoleti non diventa mai una regola di negazione; le regole ordinarie e le concessioni permanenti restano percorsi di riserva.

Algoritmo del produttore

Usa questo flusso indipendente dal linguaggio per ogni aggiornamento di un membro:

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)

Elenco di controllo per la certificazione

Completa queste verifiche in una struttura di staging usando la mappatura scope_key definitiva:

  • Una finestra assoluta viene accettata con il fuso orario omesso e viene valutata in UTC.
  • Una finestra assoluta con fuso orario IANA personalizzato mantiene gli stessi istanti from/to.
  • Una finestra settimanale con fuso orario IANA personalizzato segue l'ora locale durante il passaggio all'ora legale e il ritorno all'ora solare.
  • Un'istantanea mista assoluta/settimanale sostituisce completamente l'istantanea precedente.
  • L'accesso inizia esattamente a from e termina esattamente a to; anche le fini dei blocchi settimanali sono escluse.
  • L'aggiornamento degli orari con lo stesso external_id e una revisione più recente ha effetto.
  • L'omissione di access_time_windows modifica gli altri campi del Member senza modificare le finestre.
  • La ripetizione della stessa revisione/dello stesso payload è idempotente.
  • La stessa revisione con un payload diverso restituisce un conflitto e non modifica l'istantanea.
  • Una revisione più vecchia arrivata fuori ordine viene segnalata come obsoleta e non può riportare indietro lo stato.
  • Un [] più recente revoca le finestre di proprietà della fonte; un errore di recupero a monte non invia nulla.
  • Uno scope_key sconosciuto o non mappato nega l'accesso e genera un avviso visibile agli operatori.
  • Gli avvisi sull'età dei dati di origine e i criteri configurati per i dati obsoleti funzionano alla soglia concordata.
  • I percorsi del webhook e della riconciliazione completa convergono sulla stessa istantanea e sullo stesso ordine delle revisioni.
  • La consegna del flusso raggiunge Door & Gate Access entro l'obiettivo concordato.
  • Una finestra attiva può consentire l'accesso alla porta fisica mappata, ma non può aggirare la disattivazione dell'utente, blockAllAccess, la manutenzione o il geofencing.
  • Prima, al di fuori e dopo una finestra, le regole ordinarie e le concessioni permanenti continuano a consentire l'accesso agli utenti che ne hanno comunque diritto.

Vettori di test minimi

[
  {
    "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."
  }
]

Invia anche vettori negativi per millisecondi Unix, stringhe numeriche, numeri in virgola mobile, fusi IANA non validi, from >= to, external_id duplicati, blocchi sovrapposti, blocchi notturni, più di otto blocchi in un giorno, più di 50 finestre e chiavi di ambito non mappate.