feat(B03): 업로드 자료 카드 그래픽 미리보기 — 범위 사각형·노선 형상·범위 대조

- 계획노선 shapefile 분석에 `preview_path`(200점 안팎 솎은 좌표열) 추가 — 이미
  메모리에 있는 정점을 쓰므로 파일 재열람 0회(솎기 0.136ms)
- LAS·GeoTIFF 는 사용자 확정대로 **bbox 사각형만** (점구름·래스터 렌더 안 함)
- `upload-overview` 응답에 분석 `metadata` 동봉 — 재접속해도 같은 그림
- 카드에 SVG 미리보기 렌더(신규 `B03_FileInput_UI_Preview.ts`), 값 없으면 미표시
- 계획노선 카드는 지형 자료 범위와 대조 — 벗어나면 경고색·안내, 좌표계가 다르면
  대조 생략(재투영 안 함). shapefile 은 좌표계가 없어 같은 세트 `.prj` 값을 씀

검증: 공용 브라우저 실측(노선 선 121점·범위 안/밖·좌표계 상이 3갈래),
tmp/tests/test_b03_preview_path.py 2건, tsc --noEmit 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 07:04:33 +09:00
co-authored by Claude Opus 5
parent af85dea714
commit ab16361125
10 changed files with 337 additions and 2 deletions
+14
View File
@@ -781,6 +781,19 @@ async def get_project_upload_status(
)
def _parse_metadata(raw: Any) -> dict[str, Any] | None:
"""DB에 JSON 문자열로 저장된 분석 메타데이터를 dict로 돌린다(깨지면 생략)."""
if isinstance(raw, dict):
return raw
if not raw:
return None
try:
parsed = json.loads(raw)
except (TypeError, ValueError):
return None
return parsed if isinstance(parsed, dict) else None
@router.get("/{project_id}/upload-overview", response_model=UploadOverviewResponse)
async def get_project_upload_overview(
project_id: UUID,
@@ -821,6 +834,7 @@ async def get_project_upload_overview(
status=str(row["status"]),
uploaded_at=str(row["upload_at"]) if row.get("upload_at") else None,
relative_path=(str(row["raw_file_path"]) if row.get("raw_file_path") else None),
metadata=_parse_metadata(row.get("metadata")),
)
for row in files
],