Prioridade de Geofences Empilhados
Quando geofences de politica municipal sobrepoem zonas de operador, alguem precisa ganhar. O Levy usa uma escada de prioridade de 6 niveis, materializada como uma coluna inteira priority em cada zona, e resolvida no momento de leitura via um RPC PostGIS que retorna todas as zonas em um ponto ordenadas por prioridade descendente.
A escada de 6 niveis
Maior prioridade ganha. A primeira regra nao-nula para cada rule_type se torna a regra ativa naquele ponto.
| Nivel | Prioridade | Fonte | Exemplos |
|---|---|---|---|
| 1 | 1000 | Politica municipal prohibited_areas + speed_limit | "Sem pilotar na Pearl Street", "5 km/h na praca" |
| 2 | 950 | Politica municipal parking_zones + equity_zones | "Estacionar so em corredores pintados", "implantar >= 20% na zona X" |
| 3 | 700 | Zonas no-ride definidas pelo operador | Seu proprio no-go ao redor de propriedade privada |
| 4 | 500 | Zonas slow definidas pelo operador | Seu proprio limite de velocidade em area de pedestres |
| 5 | 300 | Zonas de estacionamento definidas pelo operador | Suas proprias definicoes de corredor |
| 6 | 100 | Padroes do sistema | Regras de velocidade e estacionamento de fallback no nivel da subconta |
Os valores numericos sao deliberadamente espacados em 50-200 para que operadores possam inserir uma zona de prioridade intermediaria personalizada se precisarem (por exemplo, uma zona temporaria de evento VIP em prioridade 800). Hoje a UI nao expoe edicao de prioridade personalizada -- toda zona de operador recebe seu padrao de nivel -- mas a coluna permite.
O resolvedor de conflitos
src/lib/compliance/conflict-resolver.ts expoe duas funcoes:
| Funcao | O que faz |
|---|---|
resolveStack(zones) | Recebe uma lista de zonas em um ponto, retorna a regra ativa por rule_type (um limite de velocidade, um flag no-ride, uma regra de estacionamento). |
detectOperatorOverrides(operatorZones, policyGeofences) | Retorna as zonas de operador atualmente sombreadas por uma politica de prioridade maior. |
resolveStack() e a funcao que alimenta current_speed_limit_kph em respostas MDS e a ordem de prioridade do geofencing_zones.json GBFS. Tambem e a funcao que o motor de cruzamento de zona chama em cada atualizacao de telemetria GPS durante uma viagem ativa.
detectOperatorOverrides() alimenta o banner de conflito no dashboard de compliance.
O RPC zones_containing_point
Migracao 20270601004000_07_zones_priority_source.sql envia uma funcao Postgres:
CREATE FUNCTION zones_containing_point(
p_subaccount_id uuid,
p_lat double precision,
p_lng double precision
)
RETURNS SETOF zones
LANGUAGE sql STABLE
AS $$
SELECT *
FROM zones
WHERE subaccount_id = p_subaccount_id
AND deleted_at IS NULL
AND ST_Contains(geom, ST_SetSRID(ST_MakePoint(p_lng, p_lat), 4326))
ORDER BY priority DESC, created_at ASC;
$$;
A coluna geom e auto-populada da coluna geojson via gatilho. Toda zona, seja escrita pelo operador ou espelhada de uma politica, tem uma geom nao-nula, entao a query PostGIS e a resposta canonica para "quais zonas contem este ponto?".
stackForPoint(point)
Em JS, src/lib/compliance/zone-stack.ts embala o RPC e adicionalmente consulta policy_geofences (que atualmente usa pointInPolygon em JS porque a coluna PostGIS nessa tabela fica nula aguardando a migracao ST_GeomFromGeoJSON). A lista combinada e entao entregue a resolveStack().
const stack = await stackForPoint({
subaccountId,
lat: vehicle.last_lat,
lng: vehicle.last_lng,
});
// stack: [
// { source: 'city', priority: 1000, rule_type: 'speed', rule_value: 8, ... },
// { source: 'operator', priority: 500, rule_type: 'speed', rule_value: 10, ... },
// { source: 'operator', priority: 300, rule_type: 'parking', ... },
// ]
const active = resolveStack(stack);
// active.speed.rule_value === 8 // cidade ganha
// active.parking existe // estacionamento do operador ainda se aplica (cidade nao tem regra de estacionamento aqui)
Deteccao de conflito
detectOperatorOverrides() e chamado pelo dashboard do operador para mostrar o banner de alerta. Retorna, para a subconta dada, todas as zonas de operador cuja geometria intersecta uma linha policy_geofences ativa de prioridade maior e o mesmo rule_type.
| Campo no resultado | O que significa |
|---|---|
operator_zone | A zona sombreada |
shadowing_rule | A regra de Policy MDS que a sobrepoe |
affected_area | A intersecao de poligono (para a previa do mapa) |
rule_type | O tipo de regra onde o conflito existe (speed, no_ride, parking) |
Na UI, o operador pode:
- Deletar a zona sombreada -- se agora e redundante com a politica municipal.
- Editar para encaixar ao lado -- se a zona do operador existe por uma razao diferente (por exemplo, uma slow zone que envolve a zona no-go da cidade, fornecendo contexto para pilotos).
- Reconhecer -- descartar o alerta sem mudar nada. O override permanece; o banner apenas para de incomodar.
Lendo a escada em clientes moveis
O app movel do cliente le o geofencing_zones.json GBFS 3.0 (ordenado por prioridade descendente) e trata o primeiro feature correspondente como a regra ativa. Isso e o que validadores GBFS esperam, e corresponde a semantica da escada: a regra municipal vem primeiro porque tem a prioridade mais alta.
Se seu cliente movel implementa sua propria resolucao de zona, a mesma regra se aplica -- ordene por prioridade descendente, pegue o primeiro feature correspondente para cada rule_type.
Cenarios
Cenario 1: slow zone municipal sobrepoe slow zone do operador
| Zona | Fonte | Prioridade | Limite de velocidade |
|---|---|---|---|
| Cidade "Pedestre do centro" | Cidade | 1000 | 8 km/h |
| Operador "Plaza" | Operador | 500 | 10 km/h |
Resultado: os 8 km/h da cidade ganham na sobreposicao. Fora da zona municipal mas dentro da zona do operador, o limite de 10 km/h se aplica. A zona do operador aparece no banner de conflito como "sombreada por Cidade Pedestre do centro".
Cenario 2: no-go municipal dentro de zona de estacionamento do operador
| Zona | Fonte | Prioridade | Tipo |
|---|---|---|---|
| Cidade "Canteiro de obras" | Cidade | 1000 | no_ride |
| Operador "Corredor Main Street" | Operador | 300 | parking |
Resultado: as regras sao de tipos diferentes, entao ambas se aplicam. Dentro do canteiro de obras, o veiculo nao pode pilotar (cidade no_ride ganha para no_ride rule_type). Fora do canteiro de obras mas dentro da zona do operador, estacionamento e permitido (estacionamento do operador e a unica regra parking).
Cenario 3: politica municipal expira
Quando policy_geofences.is_active vira false (porque active_until passou), o RPC zones_containing_point para de retornar essas linhas. A zona do operador embaixo volta ao topo da escada automaticamente. Sem limpeza manual; sem atualizacao do log de auditoria; o banner de conflito limpa no proximo carregamento de pagina.
Cenario 4: duas politicas municipais se sobrepoem
Cidades as vezes publicam duas politicas com geometria sobreposta (por exemplo, uma slow zone permanente mais uma zona no-ride temporaria de evento). Ambas estao na prioridade 1000 (nivel cidade), mas o resolvedor ordena empates por severidade de rule_type: no_ride supera speed, speed supera parking. A regra mais restrita se aplica.
Se o mesmo rule_type colide na mesma prioridade, ordenamos por start_date descendente -- a politica ativada mais recentemente ganha. Isso lida com "cidade atualizou seu limite de velocidade de slow zone de 10 para 8 km/h" de forma limpa.
O que voce pode mudar
| Botao | Padrao | Notas |
|---|---|---|
| Prioridade de zona de operador | Padrao de nivel (700/500/300) | Valores personalizados exigem atualizacao direta do DB hoje. |
| Reconhecer um conflito | n/a | Acao do dashboard; o override permanece mas o banner para de incomodar. |
| Deletar a zona sombreada | n/a | Acao do dashboard; zonas municipais espelhadas nao sao delete-aveis da UI do operador. |
Zonas municipais nao podem ser editadas pelo operador. Pertencem ao feed de politica; o unico modo de muda-las e pedir a cidade para atualizar seu feed.
Proximos passos
- Aplicacao de Velocidade em Tempo Real -- o que acontece com veiculos ja em uma zona quando a escada muda.
- Ingestao de Politica de Cidades -- de onde vem as zonas de nivel cidade.
- Como zonas funcionam -- a implementacao original de zona de operador sobre a qual a escada e construida.