본문으로 건너뛰기

Ontology GUI Modeler — M2 종결 (Step 1~5)

본 가이드는 Epic #993 의 M2 Step 1 시점에서 활성화된 온톨로지 진입점을 다룹니다. M1 (#1020/#1023/#1045/#1049/#1053) 에서 백엔드 모델·LinkML loader·SHACL validator·ArangoDB instance repo 까지 완성된 위에, M2 Step 1 은 라우터 wire-up + 사이드바 메뉴 stub 으로 기능을 사용자에게 보이게 만듭니다.

범위 한정: M2 Step 1 은 "기능이 보이는 상태" 까지가 목적입니다. React Flow 기반 Designer 캔버스, Property 인라인 편집, Relation 편집, AQL traversal, 자동 매핑 LLM 은 M2 Step 2 (#1097) ~ M2 Step 5 (#1100) 에서 점진적으로 활성화됩니다.

진입 경로

경로설명
사이드바 → Platform → 온톨로지사용자 진입점. M1 종결 후 가시성 0 이던 갭 해소.
GET /api/v1/ontology/classesClass 목록. CLI / MCP / Agent 가 같은 경로 사용.
MCP 도구 (M3 예정)LLM Agent 가 온톨로지 컨텍스트를 가져오는 경로.

핵심 동작 (M2 Step 1)

Class CRUD

  • 목록: GET /api/v1/ontology/classes?layer=L2&workspace_id=...&pack_id=...
  • 생성: POST /api/v1/ontology/classes
    • 필수: layer, name
    • 선택: label_ko, label_en, description, parent_class_id, abstract, version_id, data_source
    • ADR-006 Layer scope (L1/L2 → pack/ws 양쪽 NULL, L3 → pack 만, L4 → workspace 만) Pydantic 단계에서 422 차단
    • 동일 4-tuple (layer, pack_id, workspace_id, name) UniqueConstraint 충돌 → 409
      • PG 의 NULL-in-UNIQUE 갭은 본 PR 에서 partial unique index 3종 (uq_ontology_classes_l1l2_name, ..._l3_pack_name, ..._l4_ws_name) 으로 보강
  • 조회: GET /api/v1/ontology/classes/{id} — 404 명확 분리
  • 수정: PATCH /api/v1/ontology/classes/{id}
    • extra='forbid' — 알 수 없는 필드 명시 시 422
    • explicit null 거부 ([[feedback_pydantic_patch_null_guard]]) — {"description": null}422. 필드 누락 (omit) 만 허용
  • 삭제: DELETE /api/v1/ontology/classes/{id} — 연결 Property 는 ondelete=CASCADE 로 자동 정리

Property 조회 (read-only)

  • GET /api/v1/ontology/classes/{id}/properties — Designer 가 Class 선택 시 호출. 정렬: name asc.
  • 풀 CRUD 는 M2 Step 2 의 nested PUT 으로 통합 예정.

Instance CRUD (SHACL fail-closed)

  • POST /api/v1/ontology/instances/{class_id} — payload 는 동적 (ArangoDB document). _key 권장.
  • SHACL 검증 실패 → 422 + GenD errors extension (구조화된 violation 배열)
    {
    "status": 422,
    "detail": "SHACL validation failed for Equipment",
    "code": "ontology_shacl_violation",
    "errors": [
    { "loc": "instance", "msg": "tag_id is required (sh:minCount)", "type": "shacl_violation" }
    ]
    }
    UI 의 _extractDetail + errors 배열 렌더링이 자동 처리.
  • ArangoDB 미가용 / 일반 오류 → 503 일관 변환
  • GET /api/v1/ontology/instances/{class_id}/{key} — 단건. 없으면 404.

RBAC

동작최소 권한
Class / Instance / Property 조회viewer 이상
Class 생성·수정·삭제 / Instance 생성analyst 이상
Class 단위 ABAC (M3)OntologyClassGrant 도입 후 별도

UI 동작 (/ontology)

  1. landing card: Class 목록 (layer 별 배지), 빈 상태 메시지.
  2. Layer 필터: ALL / L1 / L2 / L3 / L4.
  3. "Class 생성" 다이얼로그: L2 만 우선 활성화 (L3/L4 는 pack/workspace 선택 워크플로우 도입 후 enable).
  4. 개별 Class 삭제: AlertDialog 확인 + sonner toast.

오류 표시:

  • API 실패 시 카드 형태 inline 알림 (text-red-800).
  • mutation 결과는 sonner toast.

다음 단계 (M2 Step 2~5)

Step범위이슈상태
M2 Step 2React Flow 캔버스 + Class/Property 편집 + Designer 탭#1097MERGED ✅
M2 Step 3Relation CRUD API + Instances 탭 + AQL traversal#1098MERGED ✅
M2 Step 4자동 매핑 LLM + 카탈로그 ↔ Class 양방향 링크#1099MERGED ✅
M2 Step 5버전 스냅샷 + 풀스택 e2e + 문서 완성 (M2 종결)#1100이번 PR

M2 Step 2 — Designer (Class/Property GUI)

/ontology 목록에서 Class 의 "Edit" 버튼을 누르면 /ontology/{classId} Designer 로 진입.

Designer 구성

  • 상단 탭: Model (활성) / Mapping (Step 4) / Instances (Step 3) / Versions (Step 5)
  • 좌측 캔버스: React Flow — 현재는 단일 Class 노드 표시. M2 Step 3 에서 Relation edge 추가 예정.
  • 우측 패널: Class metadata (label_ko/en, description) + Property 리스트 (range_type, cardinality, is_identifier, unit, pii_type)
  • 저장: PUT /api/v1/ontology/classes/{id}/with-properties 단일 transaction
    • Class metadata + Property 리스트 함께 갱신
    • Property 는 name 기반 diff (INSERT / UPDATE / DELETE)
    • 동일 name 중복 → 409
    • PG IntegrityError (range_type / cardinality CHECK) → 409 (sanitized detail)
    • 일반 실패 → 500 + 서버 로그
  • 변경 표시: dirty 플래그 (zustand designer store) — 저장 버튼은 dirty 일 때만 활성

M2 Step 3 — Relation + Instances + AQL traversal

API

  • GET /api/v1/ontology/relations (옵션: source_class_id, target_class_id, layer)
  • POST /api/v1/ontology/relations (source/target Class 존재 가드 → 404, layer scope 422, dup 409)
  • DELETE /api/v1/ontology/relations/{id} (404 / 204)
  • GET /api/v1/ontology/instances/{class_id}?limit=&offset= (페이지네이션)
  • GET /api/v1/ontology/instances/{class_id}/{key}/neighbors?depth=&max_results= (AQL traversal — onto_<class>_from/_to 매치 document follow. edge collection + GRAPH traversal 은 M3 에서 확장)

UI Designer — Instances 탭 (M2 Step 3 활성화)

  • 좌측 테이블: 인스턴스 페이지네이션 (PAGE_SIZE=25)
  • 우측 패널: 선택 인스턴스의 neighbor traversal 결과 (depth=1, max_results=50)
  • 인스턴스 생성/수정은 외부 ingestion + SHACL gate 가 담당 — 본 탭은 read-only

Relation 인라인 편집 UI (React Flow edge 드래그) 는 후속 PR. M2 Step 3 의 GUI scope 는 Instances 탭 우선.

M2 Step 4 — 자동 매핑 LLM (사람 검토 게이트)

API

  • POST /api/v1/ontology/mapper/suggest body {table_fqn, top_n?} — LLM (Anthropic Claude Sonnet 4) 가 활성 Class 카탈로그 위에서 매핑 후보 N개 반환. confidence desc 정렬. prompt caching 적용 (system 메시지 + Class 카드 블록 모두 ephemeral cache_control).
  • POST /api/v1/ontology/mapper/confirm body {class_id, table_fqn} — 사람 검토 후 Class.data_source 갱신.
  • 429/529/5xx → 지수 백오프 3회 재시도. 응답 parsing 실패 / stop_reason != end_turn → 빈 후보 (UI 가 "제안 없음" 표시).
  • catalog 메타데이터 자동 fetch 는 M2 Step 4 stub (후속 PR 에서 catalog_service.describe + query_service.sample 통합).

UI — Mapping 탭

  • 활성화: Designer > Mapping
  • 사용자가 table FQN 입력 → 후보 카드 N개 표시 (confidence bar + rationale + column → property 매핑 표)
  • 신뢰도 < 0.7 경고 (Epic #993 본문의 "무인 적용 금지" 가이드라인 — 노란 배지). 무인 적용은 시스템 자체가 차단 — 사용자가 항상 "Confirm" 버튼을 명시 클릭해야 함.
  • Confirm 시 현재 Class 와 후보 class_name 일치 검증 (mismatch → toast error).

보안 / 거버넌스

  • require_analyst — LLM 호출은 토큰 비용 발생이라 viewer 차단.
  • LLM 후보는 즉시 적용되지 않음 — Class.data_source 갱신은 두 번째 명시 POST (/mapper/confirm) 만으로.
  • API 키는 settings.llm_anthropic_api_key (config.py 의 표준 키, services/llm_proxy_service 와 정합) lazy load — 미설정 시 MapperLLMError → 503.

M2 Step 5 — 버전 스냅샷 + diff (M2 종결)

API

  • POST /api/v1/ontology/versions/snapshot body {label, description?, workspace_id?, pack_id?} — 현재 Class/Property/Relation 트리를 JSON snapshot 으로 immutable 저장
  • GET /api/v1/ontology/versions?workspace_id=&pack_id=&limit= — snapshot 메타데이터 목록 (snapshot JSON 미포함, 페이로드 절감)
  • GET /api/v1/ontology/versions/{id} — snapshot detail (JSON 트리 포함)
  • GET /api/v1/ontology/versions/{before_id}/diff?to={after_id} — 두 snapshot 사이의 diff entry 리스트
  • services/ontology/version_snapshot.py: build_snapshot / diff_snapshots (set 기반 added/removed, item 별 changed_fields)
  • workspace_id / pack_id 모두 nullable — 전체 카탈로그 snapshot 도 허용 (M3 layer 별 분리)

UI Designer — Versions 탭 (M2 Step 5 활성화 — M2 종결)

  • 좌측: snapshot 목록 (라벨, 생성자, class/property/relation 카운트 배지, description)
  • 우측: 두 snapshot 선택 후 "선택한 2개 Diff" 버튼 → 추가(녹색)/삭제(빨강)/변경(노랑) 색상 구분 테이블
  • 하단: "새 Snapshot" 버튼 → label 입력 (description 선택) → POST snapshot
  • 두 snapshot 선택은 toggle 방식 — 3번째 선택 시 가장 먼저 선택된 항목이 빠짐 (작성 순서 기준, snapshot 시간 기준 아님)
  • diff 호출 시 older→newer 자동 정렬 (created_at)

거버넌스

  • snapshot 생성은 require_analyst — read 는 require_viewer
  • 모든 endpoint 가 사용자 명시 호출만 처리 — 자동 snapshot 없음 (M3 daily cron 옵션)
  • created_by = TokenPayload.safe_username — audit 추적

회귀 가드

파일범위
apps/api/tests/test_ontology_router.pyM1 빈 GET + M2 Step 1 CRUD / 422 layer scope / 409 conflict / PATCH explicit null / Instance 422+503 + M2 Step 2 nested PUT (insert/diff/dup-name/404/extra)
ui/src/stores/ontologyDesignerStore.test.tsM2 Step 2 designer store: load class+properties / add/update/remove property / save PUT
apps/api/tests/test_ontology_mapper.pyM2 Step 4 mapper service: clamp / format / parse (stop_reason / JSON / clamp / sort) / retry (429 → success / exhausts) + 라우터 통합 (suggest 503/200/empty / confirm 200/404)
apps/api/tests/test_ontology_versions.pyM2 Step 5 version snapshot: diff_kind / diff_snapshots / 라우터 (create / list / detail / diff added·changed·removed) + 422 / 404
apps/api/tests/test_protected_routers_registration.py_protected_routers 등록 보장
apps/api/tests/test_ontology_model.pyM1 Step 2 known gap → M2 Step 1 partial unique index 도입 시 회귀 가드
ui/src/stores/ontologyStore.test.tsstore loadClasses / createClass / deleteClass / setLayerFilter 단위
ui/src/components/layout/Sidebar.test.tsxPlatform 섹션에 "Ontology" 링크 존재
ui/tests/ontology-e2e.spec.tslanding 페이지 + API smoke + layer scope 422

관련

  • ADR: ADR-006 Ontology Layered Packaging
  • Epic: #993 Ontology Layer
  • M2 Step 1 PR: this commit
  • 메모리: [[project_ontology_packaging_status]], [[feedback_pydantic_patch_null_guard]], [[feedback_fastapi_detail_shape_normalize]], [[feedback_zustand_no_immer]], [[feedback_react_refresh_export]]