From 2c309445938d1d0dcac07df895faa7f6908a624e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Ign=C3=A1cio=20Fontoura=20Solla?= Date: Mon, 27 Jul 2026 01:56:24 -0300 Subject: [PATCH 1/2] B8+: escala das malhas em unidades reais, contrato do map key e degradacao MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F1 — a tolerancia de simplificacao e os digitos de quantizacao viraram politica em unidades de solo em scripts/lib/topology.mjs, consumida pelos dois scripts de geometria. Os quantis 0.35 copiados cortavam as 19 ZE de Salvador a ~0.6 m2 e os 417 municipios a ~1.78 km2; com tolerancia absoluta e a grade de ~103 m do estado, a malha das zonas caiu de 480 para 30.2 pontos/feature. bahia-municipalities segue byte-identico e o tripwire de bytes deu lugar a pontos/feature. F2 — featureMapKey passou a ser asserido sobre features reais das tres malhas comprometidas (417 codarea, 27 code, 19 slug), e o comentario de MAP_KEY_PROPERTIES agora diz o que o codigo faz: as propriedades sao mutuamente exclusivas, nao ordenadas por especificidade. F3 — a malha das zonas e um detalhe sobre o mapa, nao o mapa: se o chunk falha, Salvador cai no poligono municipal que ja esta desenhado embaixo e o resto da Bahia continua funcionando, com o motivo logado. A ressalva de aproximacao saiu do gate de comparacao e passou a depender de bundle.hasZoneMunicipalities, derivado no servidor de municipality.kind. F4 — orfaos deletados: os dois acessores sem chamador, isZoneMapKey (o servidor ja sabe a regra), o wrapper simplifyTopology e o helper polygonCount com um branch inalcancavel. MunicipalityMapNavigation colapsou em string | undefined e MunicipalitiesByMapKey em Record. --- .cursor/rules/projects/nucleos-eleitorais.mdc | 1 + AGENTS.md | 9 +- docs/plans/escala-dry-pos-b8f2.md | 54 +++++++- docs/plans/municipio-mais-proximo.md | 2 +- docs/roadmap.md | 6 +- scripts/build-bahia-geometries.mjs | 33 ++--- .../build-municipality-zone-geometries.mjs | 73 +++++------ scripts/lib/topology.mjs | 123 ++++++++++++++++++ src/components/campaign/map/BahiaMap.tsx | 29 ++++- .../campaign/map/MapFeatureReadout.tsx | 10 +- .../campaign/map/MunicipalityMapPanel.tsx | 38 +++--- src/lib/bahiaGeometriesTypes.ts | 28 ++-- src/lib/bahiaMapStyle.ts | 11 +- src/lib/bahiaMunicipalityZoneGeometries.ts | 5 - src/lib/bahiaTerritoryGeometries.ts | 3 - .../bahia-identity-territories.topo.json | 2 +- .../bahia-municipality-zones.topo.json | 2 +- src/lib/municipalityProximity.ts | 9 +- src/utilities/municipalityMapContract.ts | 10 ++ src/utilities/municipalityMapData.ts | 9 +- src/utilities/municipalityMapNavigation.ts | 25 +--- tests/helpers/featureBounds.ts | 17 +-- tests/int/bahiaGeometries.int.spec.ts | 79 ++++++++--- tests/int/municipalityMapData.int.spec.ts | 3 + tests/unit/bahiaMapStyle.unit.spec.ts | 14 +- .../municipalityMapNavigation.unit.spec.ts | 31 +---- 26 files changed, 415 insertions(+), 211 deletions(-) create mode 100644 scripts/lib/topology.mjs diff --git a/.cursor/rules/projects/nucleos-eleitorais.mdc b/.cursor/rules/projects/nucleos-eleitorais.mdc index 7e4eb6a0..9ea2f67b 100644 --- a/.cursor/rules/projects/nucleos-eleitorais.mdc +++ b/.cursor/rules/projects/nucleos-eleitorais.mdc @@ -7,6 +7,7 @@ alwaysApply: false ## Status +- **B8+ entregue em código (2026-07-27):** os quatro débitos do `/simplify` do B8 F2, e o achado central mudou de dono. **F1 — a alavanca não era a que o plano dizia.** O plano tratava "escala de chão" como algo implementado via dígitos de quantização; medindo o re-snap do artefato commitado para grades mais grossas, quantização sozinha quase não corta: ~12 m → 8.990 pontos (99% do original), ~25 m → 8.164, ~50 m → 6.219, e **mesmo na grade da malha estadual (~103 m) → 3.849 pontos, ainda 203/feature** — ~9× o irmão. Quem manda é o **limiar absoluto de Visvalingam**, que o plano citava entre parênteses. Então a política nasceu invertida: o peso absoluto **em m² de chão** é a política, os dígitos são derivados do bbox de cada topologia. `scripts/lib/topology.mjs` (**primeiro módulo de `scripts/lib/` — o B5 F2 pousa nele, não abre um segundo**) expõe `SIMPLIFY_TOLERANCE_M2` por malha, `GROUND_GRID_METRES = 102.82`, `simplifyToGroundScale`, `quantizeToGroundGrid` e `describeGroundScale`; `SIMPLIFY_QUANTILE`/`QUANTIZE_DIGITS` morreram nos dois scripts. Resultado medido: as zonas caíram de **9.120 → 574 pontos de arco (480 → 30,2/feature) e 72.310 → 7.687 bytes**; `bahia-municipalities` saiu **byte-idêntico** (a política foi semeada no valor histórico dele); territórios mudou **1 byte** (a grade passou de 102,730 para 102,822 m ao ser derivada do bbox em vez de um `1e4` fixo). **Três fatos para ninguém re-derivar:** (1) tolerância é **por malha de propósito** — a municipal (1,78 km²) chega a 17,9 pontos/feature nas zonas, mas come **5% da área de terra de Salvador (306 → 291 km²)**, que é justamente a grandeza com que o int prova que nenhum bairro foi descartado nem duplicado; as zonas ficaram em 0,06 km² e os territórios em 13,6 km² (dissolve da malha municipal, já vêm generalizados); (2) **a ordem importa** — quantizar antes de simplificar colapsa vértices em duplicatas e o `merge` posterior produz anéis quebrados (medido: Salvador dissolvendo para 24 km² em vez de 307); (3) a área das zonas pós-corte é **306,3 km²** (era ~324), dentro da janela de 280–360 km² que o int já tinha. `MAX_TOPO_BYTES` ficou e ganhou ao lado **`MAX_POINTS_PER_FEATURE = 60`** — bytes só pegam malha que ninguém simplificou (as zonas viviam a 10% do teto de bytes com 22× a densidade do irmão — 26× é a razão do passo de grade, não da densidade). `zoneNumber` saiu do artefato e do tipo. **F2:** `featureMapKey` passou a rodar sobre features **reais** das três malhas, cada uma no laço que já a varre inteira — 417 `codarea`, 27 `code`, 19 `slug` (a zona responde slug mesmo carregando o `ibgeCode` da cidade); a primeira escrita asseria a cidade dentro do laço das zonas, e o `/simplify` mostrou que isso era **uma** feature municipal asserida 19 vezes (as 19 ZE compartilham o código de Salvador) enquanto 416 municípios e 27 TIs nunca passavam por `featureMapKey`; o caso de precedência do unit usava `{ municipalitySlug, codarea }`, forma que **nenhuma malha produz** — caiu, e o comentário passou a declarar o invariante real: as três propriedades de chave são **mutuamente exclusivas**, e `ibgeCode` na malha de zonas deliberadamente não é chave. **F3:** `.catch()` na metade das zonas — e a precisão que o plano deixava otimista: como o bundle é keyed por slug, **Salvador não volta a pintar agregada**, volta como polígono sem dado e sem interação (não está em `scopedKeys`); a memoização da rejeição fica (é o estado de falha acionável do B14) e `status === 'error'` segue reservado à malha municipal. A ressalva de aproximação saiu do gate `!comparisonActive` e virou `

` condicionado a `bundle.hasZoneMunicipalities` — campo novo do contrato, derivado de `kind === 'zona'` no loader. O `/simplify` rejeitou as duas alternativas: um `isZoneMapKey` no cliente (segunda codificação da regra de `mapKeyForMunicipality`, em espaço negativo) e `zoneBreakdown.length > 0` (recria o acoplamento que o fill-in da lista de zonas vai deletar); com o campo próprio, o fill-in só apaga o `