Instalações e pessoas
O estabelecimento, o seu pessoal, as suas zonas e as suas máquinas — o mapa fixo de que depende cada leitura de temperatura.
Estas são as coisas que não se mexem: o próprio restaurante, as pessoas que lá trabalham, as zonas em que está dividido, e as máquinas que mantêm os alimentos a uma temperatura. Nada disto é um controlo.
Ainda assim, é isto que decide o que os controlos significam. Uma temperatura é um número sem veredito nenhum até ler os limiares do equipamento em que foi medida, e uma leitura só pertence a uma parte do edifício porque o equipamento diz em que zona está. Engane-se nesta página e todos os números a jusante ficam errados em silêncio.
| Coleção | Âmbito | O que é um registo |
|---|---|---|
restaurants | restaurants:read | O estabelecimento em si: nome, morada, dias de encerramento, definições. |
users | users:read | Uma conta de funcionário que regista controlos. |
areas | areas:read | Uma zona em que o estabelecimento está dividido — cozinha, câmara frigorífica, bar. |
equipment | equipment:read | Um frigorífico, um congelador, um equipamento de cadeia de frio, com os seus limiares. |
sensors | sensors:read | Uma sonda sem fios, por endereço MAC. |
cooking-equipment | cooking-equipment:read | Um forno ou outro equipamento de confeção, com os seus limiares. |
As seis partilham o envelope de instantâneo: id,
deleted, capturedAt, receivedAt, sequence e os restantes ficam ao lado
do data descrito abaixo.
restaurants
O estabelecimento a que a sua chave está limitada, como um registo. Um por restaurante, por isso esta coleção costuma ser uma única página.
| Campo | Tipo | Significado |
|---|---|---|
name | string, obrigatório | O nome do estabelecimento, tal como aparece na aplicação. |
address | string, obrigatório | A morada postal, numa só cadeia de caracteres. |
closingDays | string[], obrigatório | Os dias em que o estabelecimento está fechado. Cadeias simples, não restringidas pelo esquema. |
exceptionalClosures | object[] | Encerramentos pontuais, cada um um objeto com from e to. |
detailedAddress | object | null | A morada repartida em partes, quando a aplicação a guarda assim. |
deliveryAddress | string | null | Onde as mercadorias são entregues, quando difere de address. |
deliveryDetailedAddress | object | null | O mesmo, em partes. |
preferredLanguage | string | null | A língua em que o restaurante trabalha. |
multiArea | boolean | null | Se o estabelecimento está dividido em mais do que uma zona. |
settings | object | null | Definições da aplicação. O formato é da aplicação e vai mudando. |
{
"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
}
}
O exemplo mostra apenas a metade operacional. Ao lado dela, o registo traz a
metade comercial: subscription, subscriptions, companyName,
billingAddress, billingEmail, tvaNumber, trialEndDate, customerId,
discount. Existem, estão no documento OpenAPI, e esta página não lhos vai
explicar.
Leia os campos operacionais e deixe os comerciais em paz. Descrevem o contrato do restaurante connosco, não a sua cozinha. Não são uma API de faturação, mudam por razões que nada têm a ver com segurança alimentar, e uma integração que devolva a um cliente o estado da sua própria subscrição é um pedido de suporte à espera de acontecer.
closingDays é uma lista de cadeias de caracteres simples. O esquema não
lhes impõe formato nenhum, por isso leia-as e mostre-as; não ramifique sobre
elas e não assuma uma grafia ou uma caixa em particular.
users
As contas do pessoal que regista os controlos. É para aqui que aponta o userId
de um registo de temperatura.
| Campo | Tipo | Significado |
|---|---|---|
firstName | string, obrigatório | O nome próprio, tal como foi introduzido. |
lastName | string, obrigatório | O apelido, tal como foi introduzido. |
email | string, obrigatório | O endereço de email da conta. |
phoneNumber | string | null | Um número de contacto, quando foi dado. |
modules | string[] | Que partes da aplicação esta pessoa utiliza. Cadeias livres. |
preferredNotificationTime | string | null | A hora a que esta pessoa pediu para ser lembrada. |
isChildren | boolean | null | Uma marca interna. O esquema não lhe dá significado nenhum; não construa nada sobre ela. |
isDeleted | boolean | null | A marca de retirada da própria aplicação — e aqui admite null, ao contrário do resto. |
{
"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
}
}
Isto são dados pessoais. email, phoneNumber e os dois campos de nome
identificam uma pessoa real, e estão nesta API por uma razão: um registo de
segurança alimentar tem de dizer quem fez o controlo. É a única função que têm
aqui.
Por isso, guarde o id e resolva um nome para mostrar quando precisar de um.
Não copie os dados de contacto para as suas tabelas, os seus registos de
atividade, os seus eventos de análise ou uma ferramenta de terceiros só porque
calharam de vir no payload. O restaurante é o responsável pelo tratamento dos
dados do seu pessoal, e cada cópia que faz é mais uma pela qual ele tem agora de
responder.
O isDeleted admite null nesta coleção onde é obrigatório noutras, por isso
trate uma marca ausente como «não retirado» em vez de a deixar passar por falsa
por acidente. A diferença entre ela e o deleted do envelope está
explicada na página do catálogo.
areas
Uma zona do restaurante. Dois campos, e muito mais importante do que dois campos fazem supor.
| Campo | Tipo | Significado |
|---|---|---|
name | string, obrigatório | A zona tal como o restaurante lhe chama — cozinha, câmara frigorífica, bar. |
isDeleted | boolean, obrigatório | A marca de retirada da aplicação. Veja a página do catálogo. |
{
"id": "9c2b17ae-4d80-4f55-b3e1-0a6c8f4d2b19",
"collection": "areas",
"deleted": false,
"capturedAt": "1789301120440",
"receivedAt": "1789301126871",
"sequence": "12",
"data": {
"name": "Chambre froide",
"isDeleted": false
}
}
areaId é a forma como tudo se agrupa por zona. Os equipamentos, os
equipamentos de confeção, as fritadeiras e as tarefas de limpeza trazem todos
um, e é a única ligação entre uma leitura e uma parte do
edifício. Não há areaId nos próprios registos: chega-se lá através do
equipamento.
O areaId admite null em todos os sítios onde aparece. Um frigorífico sem
zona é normal — significa que ninguém lhe atribuiu uma, normalmente num
estabelecimento de uma só sala em que multiArea é falso. Agrupe esses em «sem
atribuição» em vez de os deitar fora.
equipment
Frigoríficos, congeladores e outros equipamentos de cadeia de frio. O registo que decide se uma temperatura era aceitável.
| Campo | Tipo | Significado |
|---|---|---|
index | integer, obrigatório | A posição que o restaurante lhe deu na sua própria lista. Ordem de apresentação, não um identificador. |
type | string, obrigatório | Que tipo de equipamento é. Uma cadeia de caracteres sem restrições — leia-a, não ramifique sobre ela. |
name | string, obrigatório | O equipamento tal como a cozinha lhe chama. |
min | number | null | A temperatura aceitável mais baixa. |
max | number | null | A temperatura aceitável mais alta. |
areaId | string | null | A zona em que está. |
equipmentSensorId | string | null | O sensor que lhe está instalado, quando há um. |
state | string | null | O estado atual do equipamento, como uma cadeia de caracteres sem restrições. |
outOfRangeCount | number | null | Uma contagem de leituras fora do intervalo que a aplicação mantém. |
isDeleted | boolean, obrigatório | A marca de retirada da aplicação. |
{
"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 são o único veredito que recebe. Um
registo de temperatura traz um valor, uma hora e o
equipamento em que foi medido — nada que diga se estava bem. -14,4 °C é uma
crise num frigorífico e uma terça-feira normal num congelador, e é este registo
que distingue os dois casos. Todos os números de conformidade que construir se
unem aqui.
Também admitem null. Quando min ou max está ausente, você não tem limiar
nenhum, e a resposta honesta para essa leitura é «desconhecido», não «dentro do
intervalo». Diga-o na sua interface em vez de assumir verde por omissão.
Os limiares são os atuais, não os que se aplicavam. O instantâneo dá-lhe o
min e o max de hoje; uma leitura de março foi julgada contra o que estava
definido em março, e o restaurante pode alterá-los a qualquer momento. Se
reavaliar leituras antigas contra os números de hoje — que é tudo o que esta API
lhe permite fazer — identifique o resultado como um cálculo seu, e não como o
que a cozinha viu.
type e state são cadeias de caracteres sem restrições. No esquema, ambos
são um único campo de texto livre sem lista nenhuma por trás. Mostre-os, agrupe
por eles se for preciso, e nunca deixe um valor não reconhecido cair em silêncio
por uma ramificação.
outOfRangeCount é o número da aplicação, não o seu. O esquema não diz que
janela é que ele cobre, e não o consegue reconstruir a partir dos instantâneos
que estão aqui. Trate-o como um sinal vindo da aplicação e, se precisar de uma
contagem que consiga defender, calcule a sua a partir das leituras e dos
limiares, e identifique-a como sua.
O equipmentSensorId aponta do equipamento para o seu sensor. O registo do
sensor aponta no sentido contrário. Os dois lados admitem null, e nenhum deles
está garantidamente preenchido só porque o outro está.
sensors
As sondas sem fios. Dois campos, ambos opcionais.
| Campo | Tipo | Significado |
|---|---|---|
macAddress | string | null | O endereço de hardware da sonda — como distingue um dispositivo físico de outro. |
sensorEquipmentId | string | null | O equipamento que este sensor vigia. |
{
"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"
}
}
Repare nos dois nomes de campo, que são imagens espelhadas e fáceis de trocar: o
equipamento traz equipmentSensorId, um sensor traz sensorEquipmentId. Leia o
errado e recebe undefined sem erro nenhum.
Aqui não há nome, não há nível de bateria e não há hora da última comunicação. O
que um sensor comunicou está em
temperature-readings, e essas estão indexadas por
equipmentId e não pelo sensor — por isso, uma sonda que se calou é
exatamente igual a uma que não tinha nada a comunicar.
cooking-equipment
Fornos, armários de manutenção a quente e o resto do lado quente. A mesma ideia
de equipment, com a maquinaria da cadeia de frio retirada.
| Campo | Tipo | Significado |
|---|---|---|
index | integer, obrigatório | Ordem de apresentação na lista do próprio restaurante. |
type | string, obrigatório | Que tipo de equipamento é. Cadeia de caracteres sem restrições. |
name | string, obrigatório | O equipamento tal como a cozinha lhe chama. |
min | number | null | A temperatura aceitável mais baixa. |
max | number | null | A temperatura aceitável mais alta. |
areaId | string | null | A zona em que está. |
isDeleted | boolean, obrigatório | A marca de retirada da aplicação. |
{
"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
}
}
Sem state, sem outOfRangeCount, sem sensor: os equipamentos de confeção são
lidos por uma pessoa, por isso não há nada a vigiá-los entre controlos. As suas
leituras são cooking-temperature-records, que apontam
para aqui com cookingEquipmentId — um nome de campo diferente do da cadeia de
frio, e o sítio habitual onde um cliente lê nada em silêncio.
Ler o mapa
Tudo o que está nesta página é pequeno, muda devagar e é preciso antes de qualquer outra coleção fazer sentido.
- Carregue-o primeiro e guarde-o. Percorra
areas,equipment,cooking-equipmentesensorsantes de percorrer qualquer coleção de registos, e resolva os nomes e os limiares a partir da sua própria cópia em vez de o fazer registo a registo. - Atualize-o na mesma. Os equipamentos são renomeados, os limiares são corrigidos, um frigorífico muda de zona. Uma cópia tirada uma vez e nunca mais percorrida afasta-se da realidade sem nunca falhar.
- Guarde identificadores, não cópias — sobretudo no caso das pessoas.
Guarde o
userIde resolva um nome no momento de o mostrar. Veja a nota sobre dados pessoais acima. - As duas marcas de eliminação, em todo o lado. O
deleteddo envelope e oisDeleteddodatasão coisas diferentes, e a página do catálogo explica qual é qual.