Commit Graph

12 Commits

Author SHA1 Message Date
Claude VM f7468b23c2 fix(uats): never block on the feature-count groupBy — cold cache froze UAT selector
The ~30s groupBy over 9.7M GisFeature rows ran synchronously on the first
/api/eterra/uats call after every redeploy (in-memory cache), freezing the
UAT autocomplete right when users reload post-deploy. Counts only feed the
decorative 'N local' badge — return the (possibly empty) cache immediately
and refresh in the background, single-flight so concurrent cold requests
don't stack 30s queries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:00:17 +03:00
Claude VM b957de77b9 feat(faza-c.2): gate legacy GisFeature writes under USE_GIS_AC
Adds gateLegacyGisWrite() helper that returns 410 when the caller is on
the api.gis.ac path (global USE_GIS_AC=1 or per-user GIS_AC_PILOT_USERS).
Wired into 13 routes covering every entry point that touches Gis*
tables on architools_postgres — directly or via parcel-sync services.

Why: yesterday 4 GisFeature rows were updated on architools_postgres
even though the scheduler is officially disabled. Root cause: pilot
user opened the legacy /geoportal UI in a stale tab and clicked
parcels; POST /api/geoportal/enrich wrote directly to the local DB.
Without a write gate, Faza H (pg_dump + REVOKE + DROP) is unsafe —
any stale tab in any user's browser can still trip writes between
freeze and DROP.

Gated routes (writes only — reads stay open for rollback ergonomics):
- /api/geoportal/enrich (POST) — the writer of the 4 rows
- /api/eterra/sync-rules (POST), /api/eterra/sync-rules/[id] (PATCH+DELETE)
- /api/eterra/sync-rules/bulk (POST)
- /api/eterra/uats (POST+PATCH)
- /api/eterra/sync (POST), /api/eterra/sync-county (POST)
- /api/eterra/sync-background (POST), /api/eterra/sync-all-counties (POST)
- /api/eterra/auto-refresh (POST), /api/eterra/refresh-all (POST)
- /api/eterra/export-layer-gpkg (POST), /api/eterra/export-bundle (POST)
  (last two trigger syncLayer write-first-then-export)

Read-only routes intentionally NOT gated: sync-status, no-geom-scan
(scanNoGeometryParcels is read-only), export-local, db-summary,
counties, search, features (GET), stats, uat-dashboard, sync-rules
(GET), sync-rules/scheduler.

Operations: after redeploy, flip USE_GIS_AC=1 in Infisical /architools
prod env and restart container. Then monitor docker logs for ~30 min:
grep "deprecated" + "/api/geoportal/enrich|/api/eterra/sync*" lines
indicate stale-tab clients that need a refresh. pg_stat_user_tables
write count on GisFeature should hit 0 within one hour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 11:00:16 +03:00
AI Assistant 14a77dd6f7 perf: cache GisFeature counts in memory (5min TTL, stale-while-revalidate)
Feature count groupBy query is expensive but data changes rarely.
First request waits for query, subsequent ones return cached instantly.
After 5min, stale cache is returned immediately while background
refresh runs. Badge "N local" is back on UAT dropdown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 13:45:33 +02:00
AI Assistant d0c1b5d48e perf: select only needed columns from GisUat, skip geometry (~100MB) 2026-03-23 13:23:23 +02:00
AI Assistant ad4c72f527 perf(parcel-sync): make GisFeature groupBy opt-in on /api/eterra/uats
The groupBy query scanning the entire GisFeature table (~30k+ rows)
was blocking the UAT list API for 25+ seconds on every page load.
Feature counts are now opt-in via ?features=true query param.
Default response is instant (just GisUat table, no joins).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:44:09 +02:00
AI Assistant 8fa89a7675 fix(parcel-sync): restore SIRUTA in dropdown, add county debug output
- Restore SIRUTA code display in parentheses next to UAT name
- PATCH response now includes debug samples (sampleUat keys, county
  raw data) visible in browser console for diagnosing matching issues
- POST endpoint now supports resync (upsert mode, safe to call again)
- Client logs full PATCH result to browser console for debugging

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 21:34:29 +02:00
AI Assistant 431291c410 fix(parcel-sync): robust county population + local feature count in dropdown
- PATCH /api/eterra/uats: handle nested responses (unwrapArray), try
  multiple field names (extractName/extractCode), log sample UAT for
  debugging, match by code first then by name
- GET /api/eterra/uats: include localFeatures count per SIRUTA via
  GisFeature groupBy query
- Dropdown: show green badge with local feature count, county with dash
- Add SKILLS.md for ParcelSync/eTerra/GIS module context

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 21:18:22 +02:00
AI Assistant 79750b2a4a fix(parcel-sync): use eTerra nomenclature API for county population
LIMITE_UAT layer lacks WORKSPACE_ID field, so the previous approach
failed silently. Now uses fetchCounties() + fetchAdminUnitsByCounty()
nomenclature API: Phase 1 fills county for UATs with existing
workspacePk, Phase 2 enumerates counties and matches by name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:56:43 +02:00
AI Assistant 86c39473a5 feat(parcel-sync): show county in UAT search dropdown via eTerra data
PATCH /api/eterra/uats fetches counties from eTerra nomenclature and
LIMITE_UAT layer, then batch-updates GisUat records with county name
and workspacePk. Auto-triggers on first eTerra connection when county
data is missing. Helps distinguish same-name UATs in different counties.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:46:13 +02:00
AI Assistant 6b8feb9075 fix: workspace resolution via ArcGIS listLayer + seed UATs from uat.json
- resolveWorkspace: use listLayer() instead of listLayerByWhere() with
  hardcoded field names. Auto-discovers admin field (ADMIN_UNIT_ID/SIRUTA)
  from ArcGIS layer metadata via buildWhere().
- resolveWorkspace: persist WORKSPACE_ID to DB on first resolution for
  fast subsequent lookups.
- UATs POST: seed from uat.json (correct SIRUTA codes) instead of eTerra
  nomenclature API (nomenPk != SIRUTA, county nomenPk != WORKSPACE_ID).
- Remove eTerra nomenclature dependency from UATs endpoint.
- Fix activeJobs Set iteration error on container restart.
- Remove unused enrichedUatsFetched ref.
2026-03-06 21:24:51 +02:00
AI Assistant ec5a866673 feat(parcel-sync): store UATs in PostgreSQL, eliminate repeated eTerra calls
- GisUat table now includes workspacePk column (created via raw SQL)
- GET /api/eterra/uats serves from PostgreSQL  instant, no eTerra login needed
- POST /api/eterra/uats triggers sync check: compares county count with DB,
  only does full eTerra fetch if data differs or DB is empty
- Frontend loads UATs from DB on mount (fast), falls back to uat.json if empty
- On eTerra connect, fires POST to sync-check; if data changed, reloads from DB
- Workspace cache populated from DB on GET for search route performance
2026-03-06 20:56:12 +02:00
AI Assistant d948e5c1cf feat(parcel-sync): county-aware UAT autocomplete with workspace resolution
- New /api/eterra/uats endpoint fetches all counties + UATs from eTerra,
  caches server-side for 1 hour, returns enriched data with county name
  and workspacePk for each UAT
- When eTerra is connected, auto-fetches enriched UAT list (replaces
  static uat.json fallback)  shows 'FELEACU (57582), CLUJ' format
- UAT autocomplete now searches both UAT name and county name
- Selected UAT stores workspacePk in state, passes it directly to
  /api/eterra/search  eliminates slow per-search county resolution
- Search route accepts optional workspacePk, falls back to resolveWorkspace()
- Dropdown shows UAT name, SIRUTA code, and county prominently
- Increased autocomplete results from 8 to 12 items
2026-03-06 20:46:44 +02:00