- Add pmtiles-webhook.ts shared helper for triggering PMTiles rebuild
- sync-county: trigger rebuild when new features synced, pass jobId to
syncLayer for sub-progress, update % after UAT completion (not before)
- sync-all-counties: same progress fix + rebuild trigger at end
- geoportal monitor: use shared helper instead of raw fetch
- weekend-deep-sync + auto-refresh: consolidate webhook code via helper
- docker-compose: default N8N_WEBHOOK_URL to pmtiles-webhook on satra:9876
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both session-store.ts and eterra-client.ts used globalThis.__eterraSessionStore
but for completely different purposes (EterraSession vs Map<string, SessionEntry>).
The Map from eterra-client made getSessionStatus() report connected: true on
server start (Map is truthy), while getSessionCredentials() returned undefined
username/password — causing "Credentiale lipsa" on sync attempts despite the
UI showing a green "Conectat" dot.
Renamed eterra-client's global keys to __eterraClientCache and
__eterraClientCleanupTimer to eliminate the collision.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The top export buttons required all primary layers to be "fresh" (<7 days)
before using the DB download path. When stale, they fell through to live
eTerra sync which requires credentials — blocking users who just want to
download existing data. Now any UAT with data in DB uses the local export
path directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 1 of unified sync scheduler:
- New Prisma model GisSyncRule: per-UAT or per-county sync frequency
rules with priority, time windows, step selection (T/C/N/E)
- CRUD API: /api/eterra/sync-rules (list, create, update, delete, bulk)
- Global default frequency via KeyValueStore
- /sync-management page with 3 tabs:
- Reguli: table with filters, add dialog (UAT search + county select)
- Status: stats cards, frequency distribution, coverage overview
- Judete: quick county-level frequency assignment
- Monitor page: link to sync management from eTerra actions section
Rule resolution: UAT-specific > county default > global default.
Scheduler engine (Phase 2) will read these rules to automate syncs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- /api/eterra/stats: lightweight polling endpoint (8 parallel Prisma queries, 30s poll)
- /api/eterra/sync-all-counties: iterates all counties in DB sequentially,
syncs TERENURI + CLADIRI + INTRAVILAN + enrichment (magic mode) per UAT
- Monitor page: live stat cards (UATs, parcels, buildings, DB size),
Sync All Romania button with progress tracking at county+UAT level
- Concurrency guard: blocks county sync while all-Romania sync runs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The eTerra connect/disconnect UI and session status were missing from the
deployed monitor page. Also, since ETERRA env vars are empty in the
container, the connect flow now accepts username/password from the UI.
This unblocks county sync which requires an active eTerra session.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same pattern as sync-background: session credentials from eTerra login
take priority, env vars are fallback only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- GET /api/eterra/counties — distinct county list from GisUat
- POST /api/eterra/sync-county — background sync all UATs in a county
(TERENURI + CLADIRI + INTRAVILAN), magic mode for enriched UATs,
concurrency guard, creates notification on completion
- In-app notification service (KeyValueStore, CRUD, unread count)
- GET/PATCH /api/notifications/app — list and mark-read endpoints
- NotificationBell component in header with popover + polling
- Monitor page: county select dropdown + SyncTestButton with customBody
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changed bg-background/95 to bg-background (opaque) and added
text-foreground to ensure input text is visible on dark theme.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Search results now JOIN GisUat to display UAT name prominently instead
of just SIRUTA codes. Map flyTo uses imperative handle instead of
stateful props that re-triggered on re-renders.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace old approach (5 stale UATs, 7-day freshness gate, no enrichment)
with new delta engine on ALL UATs:
- Quick-count + VALID_FROM delta for sync (~7s/UAT)
- Rolling doc check + early bailout for magic UATs (~10s/UAT)
- All 43 UATs in ~5-7 minutes instead of 5 UATs in 30+ minutes
- Runs Mon-Fri 1:00-5:00 AM, weekend deep sync unchanged
- Small delays (3-10s) between UATs to be gentle on eTerra
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First-run magic enrichment on partially-enriched UATs can take
30+ minutes per UAT. After first complete run, subsequent runs
will be seconds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New endpoint POST /api/eterra/refresh-all processes all 43 UATs
sequentially. UATs with >30% enrichment get magic mode, others
get base sync only. Each UAT uses the new delta engine (quick-count
+ VALID_FROM + rolling doc check). Progress tracked via progress store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of marking features enrichedAt=null and falling through to the
full enrichment flow (which downloads the entire immovable list ~5min),
the rolling doc check now merges updated PROPRIETARI/DATA_CERERE directly
into existing enrichment and returns immediately.
Also touches enrichedAt on all checked features to rotate the batch,
ensuring different features are checked on each daily run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Always call syncLayer for TERENURI/CLADIRI (not gated by isFresh)
so that quick-count + VALID_FROM delta actually run on daily syncs
- syncLayer handles efficiency internally via quick-count match
- Add 48h freshness check for no-geom import (skip if recent)
- Admin layers: skip if synced within 24h
- Log sync summary (new features, updated features)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Cluj-Napoca (54975): base mode, parcele+cladiri only (no magic)
- Feleacu (57582): magic + no-geom (full enrichment test)
- Both with elapsed timer and phase-change logging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Quick test button on /monitor page to trigger smart delta sync
(magic mode) on Cluj-Napoca and track progress via polling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix: geoportal/enrich endpoint now looks up CLADIRI_ACTIVE from DB
instead of hardcoding HAS_BUILDING=0, BUILD_LEGAL=0
- Quick-count check: skip OBJECTID comparison when remote==local count
- VALID_FROM delta: detect attribute changes on existing parcels and
mark them for re-enrichment (catches spatial validity changes)
- Early bailout: skip all eTerra API calls when 0 features need enrichment
- Rolling doc check: probe 200 oldest-enriched parcels for new
documentation activity (catches ownership/CF changes VALID_FROM misses)
- Targeted doc fetch: only fetch documentation for immovable PKs that
actually need enrichment instead of all 10k+
Daily sync cost reduced from ~300+ API calls / 1-2h to ~6-10 calls / 10-15s.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auto-deactivate monitoring when status is 'solutionat' or 'respins'
- 'Verifica acum' shows result inline (no page reload/close)
- 'Modifica' button opens edit dialog to fix petitioner/regNumber/date
- Show monitoring section even when inactive (final status visible)
- Display tracking config info (nr, date, petitioner) in detail view
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Manual trigger now only processes sync_terenuri and sync_cladiri steps.
import_nogeom and enrich are left for the regular weekend window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auto-poll every 15s when sync is running, 60s when idle
- Live status banner: running (with city/step), error list, weekend window waiting, connection error
- Highlight active city card and currently-running step with pulse animation
- Send immediate error email per failed step (not just at session end)
- Expose syncStatus/currentActivity/inWeekendWindow in API response
- Stop silently swallowing fetch/action errors — show them in the UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PMTiles was loaded via HTTP from MinIO (10.10.10.166:9002) on an HTTPS page,
causing browser mixed-content blocking — parcels invisible on geoportal.
Fixes:
- tile-cache nginx proxies /pmtiles/ → MinIO with Range header support
- PMTILES_URL changed to relative path (resolves to HTTPS automatically)
- clickableLayers includes PMTiles fill layers (click on parcels works)
- Selection highlight uses PMTiles source at z13+ (was Martin z17+ only)
- tippecanoe per-layer zoom ranges (terenuri z13-z18, cladiri z14-z18)
skips processing millions of features at z0-z12 — faster rebuild
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Buildings now color-coded on legal status in ParcelSync map view:
- Blue fill: building has legal documents (build_legal = 1)
- Red fill: building without legal documents (build_legal = 0)
Previously only parcels had status coloring; buildings were plain blue.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- TILE-SERVER-EVALUATION.md: updated to reflect current architecture (PMTiles z0-z18)
- MODULE-MAP.md: added PMTiles + tile-cache to Geoportal section
- Monitor: timeout increased to 90 min for z18 builds, description updated
- Added PROMPT-GEOPORTAL-IMPROVE.md with mega prompt for future sessions
(includes MLT check, mvt-rs evaluation prompt, operational commands)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PMTiles now covers z0-z18 (full zoom range). Martin sources kept only for
selection highlight and fallback when PMTiles not configured.
All terenuri/cladiri fill/line/label layers served from PMTiles when active.
Zero PostGIS load for tile rendering at any zoom level.
File will be ~1-2 GB but eliminates all cold-load latency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Martin now starts at z17, so z14 sample tile returned 404.
Rebuild timeout increased from 15 to 30 min for z16 builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PMTiles now covers z0-z16 (was z0-z14) for terenuri + cladiri
- Martin only serves z17-z18 (very close zoom, few features per tile)
- map-viewer: PMTiles layers serve z13-z16 for terenuri, z14-z16 for cladiri
- Labels at z16 now from PMTiles (cadastral_ref included in tiles)
- Remove failed compound index from postgis-setup.sql
This eliminates PostgreSQL as bottleneck for 99% of tile requests.
PMTiles file will be ~300-500MB (vs 104MB at z14).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rebuild: shows webhook status, then polls every 15s until PMTiles
last-modified changes, then shows success with new size/timestamp
- Warm cache: shows HIT/MISS/error breakdown after completion
- Activity log panel with timestamps, color-coded status, scrollable
- 15-minute timeout on rebuild polling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If >80% of local features would be deleted (and >100 exist), skip deletion
and log a warning. This protects against session expiry returning empty
remote results, which previously caused the sync to mark ALL local
features as "removed" and attempt to delete them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cluj-Napoca sync failed with 62,307 removed features exceeding the limit.
Batch deletions in chunks of 30,000 in both sync-service and no-geom-sync.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dashboard page showing:
- nginx tile-cache status (connections, requests)
- Martin tile server sources
- PMTiles file info (size, last modified)
- Cache HIT/MISS test on sample tiles
- Configuration summary
Action buttons:
- Rebuild PMTiles (triggers N8N webhook)
- Warm Cache (fetches common tiles from container)
Auto-refreshes every 30 seconds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs caused sync to return 0 features after 37 minutes:
1. reloginAttempted was instance-level flag — once set to true after first
401, all subsequent 401s threw immediately without retry. Moved to
per-request scope so each request can independently relogin on 401.
2. Session lastUsed never updated during pagination — after ~10 min of
paginating, the session store considered it expired and cleanup could
evict it. Added touchSession() call before every request.
3. Single eTerra client shared across all cities/steps for hours — now
creates a fresh client per city/step (session cache still avoids
unnecessary logins when session is valid).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- weekend-deep-sync.ts: fire webhook to N8N_WEBHOOK_URL after each sync cycle
(N8N triggers tippecanoe PMTiles rebuild via SSH on host)
- nginx tile-cache: add stub_status at /status, custom log format with cache status
- Add tile-cache-stats.sh: shows HIT/MISS ratio, cache size, slow tiles
- docker-compose: add N8N_WEBHOOK_URL env var
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extend PMTiles to include simplified terenuri (5m tolerance) and cladiri (3m)
- map-viewer: terenuri z13 from PMTiles, z14+ from Martin (live detail)
- map-viewer: cladiri z14 from PMTiles, z15+ from Martin
- Martin sources start at higher minzoom when PMTiles active (less DB load)
- Add warm-tile-cache.sh: pre-populate nginx cache for major cities
- Rebuild script now includes cache warming step after PMTiles upload
- Remove deprecated docker-compose version: "3.8"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Strip parcel number prefix from building labels — "291479-C1" now displays as "C1".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add nginx reverse proxy cache in front of Martin (2GB, 1h TTL, stale serving, CORS)
- Martin no longer exposes host port — all traffic routed through tile-cache on :3010
- Add PMTiles support in map-viewer.tsx (conditional: NEXT_PUBLIC_PMTILES_URL env var)
- When set: single PMTiles source for UAT + administrativ layers (z0-z14, ~5ms/tile)
- When empty: fallback to Martin tile sources (existing behavior, zero breaking change)
- Add tippecanoe Docker service (profiles: tools) for on-demand PMTiles generation
- Add rebuild-overview-tiles.sh: ogr2ogr export → tippecanoe → MinIO atomic upload
- Install pmtiles npm package for MapLibre protocol registration
Performance impact:
- nginx cache: 10-100x faster on repeat tile requests, zero PostGIS load on cache hit
- PMTiles: sub-10ms overview tiles, zero PostGIS load for z0-z14
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: martin.yaml was never mounted in docker-compose.yml — Martin ran
in auto-discovery mode which dropped cadastral_ref from gis_cladiri tiles.
Changes:
- docker-compose: mount martin.yaml, upgrade Martin v0.15→v1.4.0, use --config
- map-viewer: add cladiriLabel layer (cadastral_ref at z16+), wire into visibility
- martin.yaml: update version comment
- geoportal/: tile server evaluation doc + 3 skill files (vector tiles, PMTiles, MapLibre perf)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New fields extracted from already-fetched documentation/GIS data
(zero extra API calls, no performance impact):
- TIP_INSCRIERE: "Intabulare, drept de PROPRIETATE, dobandit prin..."
- ACT_PROPRIETATE: "hotarare judecatoreasca nr... / contract vanzare..."
- COTA_PROPRIETATE: "1/1" or fractional
- DATA_CERERE: date of registration application
- NR_CORPURI: number of building bodies on parcel
- CORPURI_DETALII: "C1:352mp, C2:248mp, C3:104mp"
- IS_CONDOMINIUM: condominium flag
- DATA_CREARE: parcel creation date in eTerra
Also fixed HAS_BUILDING: now also uses NR_CORPURI count as fallback
(was 0 for parcels where buildingMap cross-ref missed matches).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Building labels (C1/C2/C3) disabled — Martin MVT tiles don't include
cadastral_ref as a property despite the PostgreSQL view exposing it.
Root cause needs investigation (Martin config or alternative tile server).
Removed temporary debug endpoints:
- /api/eterra/debug-tile-props
- /api/eterra/debug-tile-sample
Kept /api/eterra/debug-fields (useful long-term diagnostic).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>