Relatorios de Condicoes de Licenca
A maioria das licencas municipais incluem condicoes aplicaveis -- cap de frota, percentual de implantacao em zona de equidade, SLA de reclamacoes, relatorio de viagens de fim de dia, utilizacao de corredor de estacionamento. O Levy armazena cada uma como uma linha em permit_conditions, as avalia em um cronograma e renderiza a tendencia pass/fail tanto no dashboard do operador quanto no portal da cidade.
Os cinco tipos de condicao
condition_type | O que verifica | Cadencia |
|---|---|---|
fleet_cap | COUNT(veiculos ativos na jurisdicao) <= cap | Diaria |
equity_zone_pct | % da frota implantada dentro de geografias de equidade >= limiar | Horaria |
complaint_sla | responded_at de cada reclamacao dentro de N horas de created_at | Diaria |
trip_report_eod | Um CSV de viagem e gerado diariamente as 23:59 hora local da jurisdicao | Diaria |
corral_utilization | Snapshot horario da ocupacao do corredor de estacionamento | Horaria |
Cada linha em permit_conditions carrega o jurisdiction_id, o condition_type e um JSONB config que varia por tipo.
Configurando uma condicao
Do dashboard do operador em /dashboard/compliance/{jurisdiction-id} -> aba Condicoes de licenca, clique em Adicionar condicao.
fleet_cap
{
"cap": 250,
"vehicle_types": ["scooter"],
"exclude_statuses": ["maintenance", "removed"]
}
O avaliador conta veiculos onde:
subaccount_idcorresponde a subconta da jurisdicaolast_lat/last_lngesta dentro demds_jurisdictions.geometry(ou NULL comlast_seennas ultimas 24h -- generoso para veiculos que acabaram de perder GPS)status NOT IN config.exclude_statusesmodel.vehicle_type IN config.vehicle_types(se definido)
Pass = contagem <= cap. Fail = contagem > cap. O scoreboard do dashboard mostra a contagem atual e o cap lado a lado.
equity_zone_pct
{
"threshold_pct": 20,
"equity_geography_ids": ["geo-1", "geo-2"],
"min_vehicles_required": 5
}
Pass = implantados-em-equidade / total-implantados >= threshold_pct / 100. O min_vehicles_required e um piso -- se houver menos do que isso implantados ao todo, a condicao e marcada n/a para a hora em vez de falhada (implantacoes esparsas no inicio da manha).
Geografias de equidade vem do feed de politica da cidade (regras com rule_type = 'equity_zone') ou de uma lista configurada manualmente (quando a cidade nao as publica -- comum em cidades menores). A lista manual vive no mesmo permit_conditions.config.
complaint_sla
{
"max_response_hours": 24,
"complaint_sources": ["311", "in-app", "email"]
}
O avaliador faz join em complaints para encontrar linhas onde responded_at IS NULL AND created_at < now() - interval '<hours>' (abertas e atrasadas) ou responded_at - created_at > interval '<hours>' (fechadas mas tarde). Pass = zero reclamacoes atrasadas na janela.
trip_report_eod
{
"format": "csv",
"delivery": "email",
"recipient_email": "permits@city.gov"
}
Um relatorio agendado roda as 23:59 hora local da jurisdicao e emite um CSV de viagens para o dia. Pass = o relatorio foi entregue com sucesso. Fail = e-mail rejeitou ou a linha de relatorio nao materializou.
corral_utilization
{
"min_utilization_pct": 10,
"max_utilization_pct": 90,
"exclude_corral_ids": []
}
Snapshot horario da ocupacao de cada corredor de estacionamento. Pass = todos os corredores entre o piso e teto configurados. Fail = pelo menos um corredor fora da faixa. A condicao e usada por cidades que querem garantir que estacionamento esta realmente sendo usado (extremo baixo) e que operadores nao estao superconcentrando (extremo alto).
O scoreboard
Operadores veem o scoreboard em /dashboard/compliance/{jurisdiction-id}. Cidades veem dentro do portal da cidade em /city/{slug}. A forma e a mesma:
| Condicao | Valor atual | Limiar | Status (hoje) | Taxa de pass de 30 dias |
|---|---|---|---|---|
| Cap de frota | 247 veiculos | 250 | Pass | 100% |
| Equidade % | 18% | >= 20% | Fail | 88% |
| SLA de reclamacao | 2 abertas mais de 24h | 0 | Fail | 92% |
| Relatorio viagem EOD | Entregue | n/a | Pass | 100% |
| Utilizacao de corredor | 6 de 6 na faixa | Todos na faixa | Pass | 95% |
Condicoes falhando sao destacadas em vermelho. A taxa de pass de 30 dias e computada do historico city_compliance_reports.
A biblioteca reporter
src/lib/compliance/permit-reporter.ts expoe avaliadores por condicao. Cada um recebe a jurisdicao + o permit_conditions.config relevante e retorna:
type ConditionResult = {
condition_id: string;
passed: boolean;
current_value: number | string;
threshold: number | string;
measured_at: string; // RFC3339
details: Record<string, unknown>;
};
O relatorio agregado no momento do digest:
const report = await buildComplianceReport({
jurisdictionId,
period: 'daily', // ou 'weekly' / 'monthly'
date: '2026-05-18',
});
// report.conditions: ConditionResult[]
// report.summary.passed: number
// report.summary.failed: number
// report.trips: { count, total_distance_km, ... }
// report.complaints: { open, closed, late, ... }
// report.enforcement_events: { speed_limits_applied, locks_issued, ... }
Esta forma e o que o e-mail de digest renderiza, o que a API de compliance-report serve e o que e persistido para city_compliance_reports.payload.
A API de compliance-report
Contatos municipais puxam relatorios formais de:
GET /api/city/{slug}/compliance-report?period=monthly&date=2026-05
Retorna a mesma forma JSON que o digest, mais um pdf_url quando period=monthly (renderizamos um PDF de uma pagina para relatorios mensais -- util para renovacoes de licenca).
Cada relatorio buscado e persistido em city_compliance_reports -- a trilha de auditoria de cada relatorio que a cidade realmente recuperou. Se um auditor municipal pergunta "voces enviaram um relatorio para janeiro?", a linha em city_compliance_reports e a resposta.
Quando condicoes falham
Uma condicao falhando nao pausa automaticamente sua frota. Ela e exposta como:
- Uma linha vermelha no scoreboard
- Uma linha no digest de hoje
- Um breadcrumb Sentry marcado
compliance.condition.failed
O que voce faz a respeito depende da condicao. Para excesso de fleet_cap, implante menos veiculos. Para equity_zone_pct, redistribua. Para complaint_sla, responda as reclamacoes abertas. O sistema nao toma decisoes operacionais por voce.
A excecao e fleet_cap, onde o dashboard oferece um toggle "Pausar novas viagens iniciando na jurisdicao". Esse e um soft constraint -- viagens existentes continuam, nenhuma nova viagem comeca na jurisdicao ate o cap voltar a ficar abaixo do limiar. O toggle e controlado pelo operador; nao o ativamos automaticamente porque pausas auto falsas-positivas seriam muito piores que um fail de fleet_cap transiente.
Condicoes customizadas
Se sua licenca inclui uma condicao que nao se encaixa nos cinco tipos embutidos (por exemplo, "deve atingir 90% de sinalizacao multilingue"), adicione uma linha com condition_type = 'custom' e um campo passed manual. O avaliador retorna o que voce define; cidades ainda veem no scoreboard. Esta e a saida de emergencia para licencas com uma clausula estranha.
Proximos passos
- E-mails de Digest e Cadencia -- quando esses resultados chegam na caixa da cidade.
- Portal da Cidade e Magic-Link Auth -- onde o contato municipal ve o scoreboard.