Strutture e persone
La sede, il suo personale, le sue zone e le sue macchine — la mappa fissa a cui si aggancia ogni lettura di temperatura.
Queste sono le cose che non si muovono: il ristorante stesso, le persone che ci lavorano, le zone in cui è suddiviso, e le macchine che tengono il cibo a una temperatura. Niente di tutto ciò è un controllo.
Decide comunque cosa significano i controlli. Una temperatura è un numero senza alcun verdetto allegato finché non legge le soglie sull'apparecchiatura su cui è stata rilevata, e una lettura appartiene a una parte dell'edificio solo perché l'apparecchiatura dice in quale area si trova. Sbagli questa pagina e ogni dato a valle è sbagliato in silenzio.
| Collezione | Ambito | Che cos'è un record |
|---|---|---|
restaurants | restaurants:read | La sede stessa: nome, indirizzo, giorni di chiusura, impostazioni. |
users | users:read | Un account del personale che registra i controlli. |
areas | areas:read | Una zona in cui la sede è suddivisa — cucina, cella frigorifera, bar. |
equipment | equipment:read | Un frigorifero, un congelatore, un'unità della catena del freddo, con le sue soglie. |
sensors | sensors:read | Una sonda wireless, per indirizzo MAC. |
cooking-equipment | cooking-equipment:read | Un forno o un'altra unità di cottura, con le sue soglie. |
Tutte e sei condividono l'involucro di istantanea: id,
deleted, capturedAt, receivedAt, sequence e gli altri stanno accanto al
data descritto qui sotto.
restaurants
La sede a cui è circoscritta la sua chiave, come record. Uno per ristorante, quindi questa collezione è di solito una pagina sola.
| Campo | Tipo | Significato |
|---|---|---|
name | stringa, obbligatorio | Il nome della sede, come viene mostrato nell'app. |
address | stringa, obbligatorio | L'indirizzo postale, come stringa unica. |
closingDays | stringa[], obbligatorio | I giorni in cui la sede è chiusa. Semplici stringhe, non vincolate dallo schema. |
exceptionalClosures | oggetto[] | Chiusure straordinarie, ciascuna un oggetto con from e to. |
detailedAddress | oggetto | null | L'indirizzo scomposto in parti, quando l'app lo tiene così. |
deliveryAddress | stringa | null | Dove vengono consegnate le merci, quando differisce da address. |
deliveryDetailedAddress | oggetto | null | Lo stesso, in parti. |
preferredLanguage | stringa | null | La lingua in cui lavora il ristorante. |
multiArea | booleano | null | Se la sede è suddivisa in più di un'area. |
settings | oggetto | null | Impostazioni dell'app. La forma è dell'app e si muove. |
{
"id": "e5a1d3c9-7b40-4a28-9df6-18c0b2e46a75",
"collection": "restaurants",
"deleted": false,
"capturedAt": "1789300481002",
"receivedAt": "1789300489517",
"sequence": "7",
"data": {
"name": "Le Comptoir de Nanterre",
"address": "12 rue des Anciennes Mairies, 92000 Nanterre",
"closingDays": ["sunday"],
"preferredLanguage": "fr",
"multiArea": true
}
}
L'esempio mostra solo la metà operativa. Accanto a essa il record porta quella
commerciale: subscription, subscriptions, companyName, billingAddress,
billingEmail, tvaNumber, trialEndDate, customerId, discount.
Esistono, sono nel documento OpenAPI, e questa pagina non glieli illustrerà.
Legga i campi operativi e lasci stare quelli commerciali. Descrivono il contratto del ristorante con noi, non la sua cucina. Non sono un'API di fatturazione, cambiano per ragioni che non hanno nulla a che fare con la sicurezza alimentare, e un'integrazione che restituisce a un cliente lo stato del suo abbonamento è un ticket di assistenza in attesa di accadere.
closingDays è un elenco di semplici stringhe. Lo schema non impone loro
alcuna forma, quindi le legga e le mostri; non ci faccia uno switch sopra e non
dia per scontata una particolare grafia o un particolare uso delle maiuscole.
users
Gli account del personale che registra i controlli. È a questi che punta lo
userId di un record di temperatura.
| Campo | Tipo | Significato |
|---|---|---|
firstName | stringa, obbligatorio | Nome di battesimo, così come inserito. |
lastName | stringa, obbligatorio | Cognome, così come inserito. |
email | stringa, obbligatorio | L'indirizzo email dell'account. |
phoneNumber | stringa | null | Un recapito telefonico, quando ne è stato fornito uno. |
modules | stringa[] | Quali parti dell'app usa questa persona. Stringhe libere. |
preferredNotificationTime | stringa | null | Quando questa persona ha chiesto di ricevere un promemoria. |
isChildren | booleano | null | Un indicatore interno. Lo schema non gli dà alcun significato; non ci costruisca sopra. |
isDeleted | booleano | null | L'indicatore di dismissione proprio dell'app — e qui ammette null, a differenza che altrove. |
{
"id": "2774953d-8d9b-4a68-8ec4-1209edd90777",
"collection": "users",
"deleted": false,
"capturedAt": "1789315002664",
"receivedAt": "1789315010218",
"sequence": "31",
"data": {
"firstName": "Amina",
"lastName": "Berthier",
"email": "amina.berthier@example.test",
"phoneNumber": null,
"modules": ["temperatures", "cleaning"],
"isDeleted": false
}
}
Questi sono dati personali. email, phoneNumber e i due campi del nome
identificano una persona reale, e sono in questa API per un solo motivo: un
record di sicurezza alimentare deve dire chi ha effettuato il controllo. È
l'unico compito che hanno qui.
Quindi memorizzi l'id e risolva un nome da mostrare quando le serve. Non copi
i recapiti nelle sue tabelle, nei suoi log, nei suoi eventi di analytics o in
uno strumento di terze parti solo perché si trovavano nel payload. Il
ristorante è il titolare del trattamento per il suo personale, e ogni copia che
lei fa è una copia di cui adesso deve rendere conto.
isDeleted ammette null in questa collezione dove altrove è obbligatorio,
quindi tratti un indicatore mancante come «non dismesso» anziché lasciarlo
passare per errore come qualcosa di simile a false. La differenza fra questo e
il deleted dell'involucro è
spiegata nella pagina del catalogo.
areas
Una zona del ristorante. Due campi, e molto più importante di quanto due campi lascino pensare.
| Campo | Tipo | Significato |
|---|---|---|
name | stringa, obbligatorio | La zona come la nomina il ristorante — cucina, cella frigorifera, bar. |
isDeleted | booleano, obbligatorio | L'indicatore di dismissione dell'app. Veda la pagina del catalogo. |
{
"id": "9c2b17ae-4d80-4f55-b3e1-0a6c8f4d2b19",
"collection": "areas",
"deleted": false,
"capturedAt": "1789301120440",
"receivedAt": "1789301126871",
"sequence": "12",
"data": {
"name": "Chambre froide",
"isDeleted": false
}
}
areaId è il modo in cui tutto viene raggruppato per zona. Apparecchiature,
apparecchiature di cottura, friggitrici e compiti di pulizia ne portano tutti
uno, ed è l'unico collegamento fra una lettura e una parte
dell'edificio. Sui record stessi non c'è alcun areaId: ci si arriva passando
per l'apparecchiatura.
areaId ammette null ovunque compaia. Un frigorifero senza area è normale —
significa che nessuno gliel'ha assegnata, di solito in una sede a locale unico
dove multiArea è false. Raggruppi quelli sotto «non assegnati» anziché
scartarli.
equipment
Frigoriferi, congelatori e altre unità della catena del freddo. Il record che decide se una temperatura fosse accettabile.
| Campo | Tipo | Significato |
|---|---|---|
index | intero, obbligatorio | La posizione che il ristorante le ha dato nel proprio elenco. Ordine di visualizzazione, non un identificatore. |
type | stringa, obbligatorio | Che tipo di unità è. Una stringa non vincolata — la legga, non ci faccia uno switch sopra. |
name | stringa, obbligatorio | L'unità come la chiama la cucina. |
min | numero | null | La temperatura accettabile più bassa. |
max | numero | null | La temperatura accettabile più alta. |
areaId | stringa | null | L'area in cui si trova. |
equipmentSensorId | stringa | null | Il sensore installato su di essa, quando ce n'è uno. |
state | stringa | null | Lo stato attuale dell'unità, come stringa non vincolata. |
outOfRangeCount | numero | null | Un conteggio di letture fuori intervallo che l'app mantiene. |
isDeleted | booleano, obbligatorio | L'indicatore di dismissione dell'app. |
{
"id": "84ce5c13-3237-40d4-901a-cbba59a6406f",
"collection": "equipment",
"deleted": false,
"capturedAt": "1789302455901",
"receivedAt": "1789302461330",
"sequence": "58",
"data": {
"index": 2,
"type": "freezer",
"name": "Congélateur bas",
"min": -22,
"max": -18,
"areaId": "9c2b17ae-4d80-4f55-b3e1-0a6c8f4d2b19",
"equipmentSensorId": "b31f7d64-0e29-42ca-9a57-51d3c8e07f28",
"state": "ok",
"outOfRangeCount": 0,
"isDeleted": false
}
}
min e max sono l'unico verdetto che ottiene. Un
record di temperatura porta un valore, un'ora e
l'apparecchiatura su cui è stato rilevato — niente che dica se andasse bene.
-14,4 °C è una crisi in un frigorifero e un martedì normale in un congelatore, e
questo record è ciò che distingue le due cose. Ogni dato di conformità che
costruisce si unisce qui.
Ammettono anche null. Quando min o max manca lei non ha alcuna soglia, e la
risposta onesta per quella lettura è «sconosciuto», non «nell'intervallo». Lo
dica nella sua interfaccia anziché ripiegare sul verde.
Le soglie sono quelle attuali, non quelle che si applicavano. L'istantanea
le dà il min e il max di oggi; una lettura di marzo è stata giudicata
rispetto a ciò che era impostato a marzo, e il ristorante può cambiarli in
qualsiasi momento. Se rivaluta vecchie letture rispetto ai numeri di oggi — che
è tutto ciò che questa API le consente di fare — etichetti il risultato come il
suo ricalcolo, non come ciò che ha visto la cucina.
type e state sono stringhe non vincolate. Nello schema sono entrambi
singoli campi di testo libero senza alcun elenco alle spalle. Li mostri, ci
raggruppi sopra se deve, e non lasci mai che un valore non riconosciuto scivoli
in silenzio attraverso uno switch.
outOfRangeCount è il numero dell'app, non il suo. Lo schema non dice quale
finestra copra, e lei non può ricostruirlo dalle istantanee che trova qui. Lo
tratti come un segnale dell'app e, se le serve un conteggio che possa
difendere, calcoli il suo a partire dalle letture e dalle soglie e lo etichetti
come suo.
equipmentSensorId punta dall'apparecchiatura al suo sensore. Il record del
sensore punta nell'altra direzione. Entrambi i lati ammettono null, e non è
garantito che uno sia compilato solo perché lo è l'altro.
sensors
Le sonde wireless. Due campi, entrambi opzionali.
| Campo | Tipo | Significato |
|---|---|---|
macAddress | stringa | null | L'indirizzo hardware della sonda — come distingue un dispositivo fisico da un altro. |
sensorEquipmentId | stringa | null | L'apparecchiatura che questo sensore sorveglia. |
{
"id": "b31f7d64-0e29-42ca-9a57-51d3c8e07f28",
"collection": "sensors",
"deleted": false,
"capturedAt": "1789302501764",
"receivedAt": "1789302509002",
"sequence": "61",
"data": {
"macAddress": "F4:12:9D:3A:77:0B",
"sensorEquipmentId": "84ce5c13-3237-40d4-901a-cbba59a6406f"
}
}
Noti i due nomi di campo, che sono l'immagine speculare l'uno dell'altro e sono
facili da invertire: l'apparecchiatura porta equipmentSensorId, un sensore
porta sensorEquipmentId. Legga quello sbagliato e ottiene undefined senza
alcun errore.
Qui non c'è alcun nome, alcun livello della batteria e alcun orario di ultimo
contatto. Cosa ha riportato un sensore sta su
temperature-readings, e quelle sono indicizzate per
equipmentId anziché per sensore — quindi una sonda che è ammutolita è
identica a una che non aveva nulla da riportare.
cooking-equipment
Forni, armadi di mantenimento e il resto del lato caldo. Stessa idea di
equipment, con la meccanica della catena del freddo tolta di mezzo.
| Campo | Tipo | Significato |
|---|---|---|
index | intero, obbligatorio | Ordine di visualizzazione nell'elenco del ristorante. |
type | stringa, obbligatorio | Che tipo di unità è. Stringa non vincolata. |
name | stringa, obbligatorio | L'unità come la chiama la cucina. |
min | numero | null | La temperatura accettabile più bassa. |
max | numero | null | La temperatura accettabile più alta. |
areaId | stringa | null | L'area in cui si trova. |
isDeleted | booleano, obbligatorio | L'indicatore di dismissione dell'app. |
{
"id": "3d90f4c8-62b7-4e13-8a05-ff71d6b9c204",
"collection": "cooking-equipment",
"deleted": false,
"capturedAt": "1789303880115",
"receivedAt": "1789303887640",
"sequence": "64",
"data": {
"index": 1,
"type": "oven",
"name": "Four à sole",
"min": 63,
"max": 260,
"areaId": "9c2b17ae-4d80-4f55-b3e1-0a6c8f4d2b19",
"isDeleted": false
}
}
Nessuno state, nessun outOfRangeCount, nessun sensore: le unità di cottura
vengono lette da una persona, quindi non c'è nulla che le sorvegli fra un
controllo e l'altro. Le loro letture sono
cooking-temperature-records, che puntano qui con
cookingEquipmentId — un nome di campo diverso da quello della catena del
freddo, e il solito punto in cui un client legge il nulla in silenzio.
Leggere la mappa
Tutto ciò che sta in questa pagina è piccolo, cambia lentamente ed è necessario prima che qualsiasi altra collezione abbia senso.
- La carichi per prima e la tenga. Percorra
areas,equipment,cooking-equipmentesensorsprima di percorrere qualsiasi collezione di record, e risolva nomi e soglie dalla sua copia anziché per ogni record. - La aggiorni comunque. Le apparecchiature vengono rinominate, le soglie corrette, un frigorifero si sposta in un'altra area. Una copia presa una volta e mai più percorsa diverge senza mai fallire.
- Conservi gli id, non le copie — soprattutto per le persone. Memorizzi lo
userIde risolva un nome al momento della visualizzazione. Veda la nota sui dati personali qui sopra. - Entrambi gli indicatori di cancellazione, ovunque. Il
deleteddell'involucro e l'isDeleteddidatasono cose diverse, e la pagina del catalogo spiega quale sia quale.