fix(geoportal-v2): siruta-aware parcela lookup (B1 round 2)

Previous fix searched by cadastralRef and picked the first
layerId-matching result. But cadastral refs collide across UATs:
"354686" exists in multiple counties. The Cluj-Napoca f9bf2ca4-...
parcel with full enrichment got passed over for a same-cad parcel
in another UAT that has no enrichment → panel rendered header +
"Caracteristici" with empty Intravilan, no "Date eTerra" section.

New server-side /api/gis/parcela/find?siruta&cad&layerId proxy:
- gisApi.search(cad) → filter by layerId → up to ~20 candidates
- For each candidate, parcela.get and check stored siruta
- Return the siruta-matching detail
- Fallback: first readable candidate (so the panel still has data
  even if siruta mismatch — better than empty)

Panel useEffect simplified: fast path = parcela.get by uuid when the
tile has one, slow path = parcela/find when not. 404 from find sets
the "not in central DB yet" empty state (user can hit Citește din
ANCPI to trigger orchestrator live-fetch).

Diagnostic logs: [gis-parcela-find] siruta=… cad=… layerId=…
candidates=N + per-hit "has_enrich=true keys=N" so we can tell from
container logs whether the right parcel resolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude VM
2026-05-19 15:26:49 +03:00
parent b5eff5acc1
commit 7afba6e1a9
2 changed files with 121 additions and 25 deletions
+16 -25
View File
@@ -165,34 +165,25 @@ export function FeatureInfoPanel({ feature, onClose, basic = false }: Props) {
const run = async () => {
try {
let id = feature.id;
if (!id) {
const sr = await fetch(
`/api/gis/search?q=${encodeURIComponent(feature.cadastralRef)}&limit=20`,
let r: Response;
if (feature.id) {
// Fast path: tile carried the uuid.
r = await fetch(`/api/gis/parcela/${encodeURIComponent(feature.id)}`);
} else {
// PMTiles overview: no uuid → use the server-side lookup that
// resolves siruta+cadref+layerId across candidate matches.
r = await fetch(
`/api/gis/parcela/find?siruta=${encodeURIComponent(feature.siruta)}` +
`&cad=${encodeURIComponent(feature.cadastralRef)}` +
`&layerId=${encodeURIComponent(feature.layerId)}`,
);
if (cancelled) return;
if (!sr.ok) {
setError("search_failed");
setLoading(false);
return;
}
const sd = (await sr.json()) as {
features?: Array<{ id: string; layerId: string; cadastralRef: string }>;
};
const match = (sd.features ?? []).find(
(f) =>
f.cadastralRef === feature.cadastralRef &&
f.layerId === feature.layerId,
);
if (!match) {
// Parcel not in central DB — show header only; user can press "Citește din ANCPI"
setLoading(false);
return;
}
id = match.id;
}
const r = await fetch(`/api/gis/parcela/${encodeURIComponent(id)}`);
if (cancelled) return;
if (r.status === 404) {
// Parcel not in central DB yet — show header only; user can hit "Citește din ANCPI"
setLoading(false);
return;
}
if (r.status === 403) {
setError("forbidden");
setLoading(false);