fix(nspd): list_layers parse flat layers[] response, not children-tree (#1077)
NSPDClient.list_layers() парсил _walk_layer_tree по id/children, но НСПД layers-theme-tree отдаёт слои в плоском layers[] с ключом layerId → list_layers() молча возвращал [] для любой темы. Fix: сначала плоский data["layers"], иначе fallback на _walk_layer_tree; defensive None-guard на missing id. +тест на flat-форму, backward-compat сохранён. Refs #1067. Co-authored-by: lekss361 <lekss361@gendsgn.local> Co-committed-by: lekss361 <lekss361@gendsgn.local>
This commit is contained in:
parent
d9c2157d02
commit
38d948f078
2 changed files with 66 additions and 6 deletions
|
|
@ -613,9 +613,24 @@ class NSPDClient:
|
||||||
# Shape defense: NSPD иногда оборачивает в {"data": [...]} — паттерн уже
|
# Shape defense: NSPD иногда оборачивает в {"data": [...]} — паттерн уже
|
||||||
# ловили в Bug_Nspd_Geo_Str_Object_No_Get_Fixed. Без проверки walker
|
# ловили в Bug_Nspd_Geo_Str_Object_No_Get_Fixed. Без проверки walker
|
||||||
# молча вернул бы [], а caller увидел бы пустой catalog.
|
# молча вернул бы [], а caller увидел бы пустой catalog.
|
||||||
if isinstance(data, dict) and "data" in data and not data.get("children"):
|
if (
|
||||||
|
isinstance(data, dict)
|
||||||
|
and "data" in data
|
||||||
|
and not data.get("children")
|
||||||
|
and "layers" not in data
|
||||||
|
):
|
||||||
data = data.get("data") or []
|
data = data.get("data") or []
|
||||||
if not isinstance(data, list | dict):
|
# Реальная форма layers-theme-tree: {"layers": [...flat...], "tree": ...},
|
||||||
|
# где каждая запись = {"layerId": N, "title", "layerName", "layerType"}.
|
||||||
|
# Раньше walker искал id/children и для этой (актуальной) формы молча
|
||||||
|
# возвращал [] — слои не в children-дереве, а в плоском layers[]
|
||||||
|
# (см. research/NSPD_IRD_Layers_Automation_Recon_Jun06). Сначала пробуем
|
||||||
|
# плоский layers[], иначе fallback на nested-tree walker (id/children).
|
||||||
|
if isinstance(data, dict) and isinstance(data.get("layers"), list):
|
||||||
|
raw_entries: list[dict[str, Any]] = data["layers"]
|
||||||
|
elif isinstance(data, list | dict):
|
||||||
|
raw_entries = _walk_layer_tree(data)
|
||||||
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"list_layers: unexpected NSPD response type %s for theme_id=%s",
|
"list_layers: unexpected NSPD response type %s for theme_id=%s",
|
||||||
type(data).__name__,
|
type(data).__name__,
|
||||||
|
|
@ -623,12 +638,14 @@ class NSPDClient:
|
||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
layers: list[NSPDLayer] = []
|
layers: list[NSPDLayer] = []
|
||||||
# Tree response: nested {"children": [...], "id": N, "title": "..."}
|
for entry in raw_entries:
|
||||||
# Используем walker по subtree чтобы вытащить все leaf layers
|
# актуальная форма использует layerId; старая nested-tree — id
|
||||||
for entry in _walk_layer_tree(data):
|
raw_id = entry.get("layerId", entry.get("id"))
|
||||||
|
if raw_id is None:
|
||||||
|
continue
|
||||||
layers.append(
|
layers.append(
|
||||||
NSPDLayer(
|
NSPDLayer(
|
||||||
layer_id=int(entry["id"]),
|
layer_id=int(raw_id),
|
||||||
title=str(entry.get("title", "")),
|
title=str(entry.get("title", "")),
|
||||||
layer_type=entry.get("layerType") or entry.get("layer_type"),
|
layer_type=entry.get("layerType") or entry.get("layer_type"),
|
||||||
metadata=entry,
|
metadata=entry,
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,49 @@ def test_list_layers_handles_garbage_response(monkeypatch: pytest.MonkeyPatch) -
|
||||||
assert NSPDClient().list_layers(theme_id=1) == []
|
assert NSPDClient().list_layers(theme_id=1) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_layers_parses_flat_layers_response(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
"""Актуальная форма NSPD: {"layers": [...flat...], "tree": ...} с ключом layerId.
|
||||||
|
|
||||||
|
Регрессия на баг: walker искал id/children, а реальный ответ держит слои в
|
||||||
|
плоском layers[] (layerId/title/layerName/layerType) — list_layers молча
|
||||||
|
возвращал [] для любой темы (см.
|
||||||
|
research/NSPD_IRD_Layers_Automation_Recon_Jun06).
|
||||||
|
"""
|
||||||
|
fake_resp = {
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"layerId": 36945,
|
||||||
|
"title": "Кадастровые округа",
|
||||||
|
"layerName": "ЕГРН. Округа",
|
||||||
|
"layerType": "wms",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"layerId": 875838,
|
||||||
|
"title": "Территориальные зоны",
|
||||||
|
"layerName": "ПЗЗ",
|
||||||
|
"layerType": "wms",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"layerId": 37577,
|
||||||
|
"title": "ЗОУИТ ОКН",
|
||||||
|
"layerName": "ЗОУИТ ОКН",
|
||||||
|
"layerType": "wms",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"tree": {"categories": []},
|
||||||
|
}
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"app.services.scrapers.nspd_client._http_get_json",
|
||||||
|
lambda url, **kw: fake_resp,
|
||||||
|
)
|
||||||
|
layers = NSPDClient().list_layers(theme_id=1)
|
||||||
|
assert sorted(layer.layer_id for layer in layers) == [36945, 37577, 875838]
|
||||||
|
tz = next(layer for layer in layers if layer.layer_id == 875838)
|
||||||
|
assert tz.title == "Территориальные зоны"
|
||||||
|
assert tz.layer_type == "wms"
|
||||||
|
assert tz.metadata["layerName"] == "ПЗЗ"
|
||||||
|
|
||||||
|
|
||||||
# ── _geojson_bbox_3857 tests ──────────────────────────────────────────────────
|
# ── _geojson_bbox_3857 tests ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue