schemalism
PART 02·of 04

표가 아니라 캔버스로 — 부적합을 노드 그래프로 그린 이유

1 NC → N RCA → N EXE 의 트리는 폼/리스트 UI 로 한 화면에 안 들어옵니다. NC·RCA·EXE 각각을 캔버스 노드로 두고 좌표를 데이터에 박은 결정, 그리고 EXE 한 row 가 노드 셋으로 쪼개진 이유.

2026년 7월 12일 · 정윤환 · 6

부적합 관리 화면을 만들면 보통 이렇게 됩니다.

  1. NC 리스트 (테이블)
  2. NC 행 클릭 → 상세 페이지
  3. 상세 페이지 안에 RCA 탭, EXE 탭
  4. 각 탭 안에 또 리스트 → “+ 추가” 모달

클릭이 깊습니다. 그리고 결정적으로, 트리가 안 보입니다. 부적합 하나에 원인이 셋이고, 그중 첫째 원인에 조치가 둘인데 둘 다 효과성 평가가 미정이라는 구조가, 폼/리스트 UI 로는 한 화면에 안 들어옵니다.

form & list

  • 탭 → 모달 → 탭
  • 1 NC → N RCA → N EXE 가 한 화면에 안 들어옴
  • 의미 묶음(사람 vs 설비)이 사라짐

canvas

  • 같은 캔버스 위에 노드
  • 좌표가 데이터에 박혀 매번 같이 복원
  • 사용자가 의도한 묶음이 살아남음

01 · Our pick

우리 선택 — 캔버스

NC, RCA, EXE 각각을 캔버스 위 노드로 두었습니다. 사용자가 카드를 끌어다 자유롭게 배치하고, 카드끼리 선으로 연결합니다.

데이터 모델에 박힌 흔적은 단순합니다.

python
class ActionNonConformity(Base):
    ...
    canvas_x = Column(Integer, nullable=True)
    canvas_y = Column(Integer, nullable=True)

class ActionRootCause(Base):
    ...
    canvas_x = Column(Integer, nullable=True)
    canvas_y = Column(Integer, nullable=True)

class ActionExecution(Base):
    ...
    canvas_x = Column(Integer, nullable=True)
    canvas_y = Column(Integer, nullable=True)
    result_canvas_x = Column(Integer, nullable=True)
    result_canvas_y = Column(Integer, nullable=True)
    effectiveness_canvas_x = Column(Integer, nullable=True)
    effectiveness_canvas_y = Column(Integer, nullable=True)

자동 배치 알고리즘으로 그릴 수도 있었습니다. 그런데 안 그랬어요. 자동 배치는 “이 두 원인은 사람 문제, 저 둘은 설비 문제” 같이 사용자가 의도한 의미 구조를 잃습니다. 부적합 캔버스에서 가장 중요한 정보가 그건데 — 누가 어떤 묶음으로 봤는지 — 자동 정렬로 갈아엎으면 그 의도가 휘발됩니다. 그래서 좌표를 사람이 직접 저장합니다. 재접속하면 똑같이 복원됩니다.

02 · 3 nodes per EXE

Execution 한 row 가 노드 셋으로 쪼개지는 이유

EXE node = plan + result + effectiveness
세 사람, 세 시점, 세 카드

같은 EXE 라도 캔버스에서는 보통 계획 노드 / 결과 노드 / 효과성 노드 세 카드로 나뉘어 보입니다. 그래서 EXE 에 canvas_x/y + result_canvas_x/y + effectiveness_canvas_x/y 세 쌍의 좌표가 있어요.

왜 한 카드가 아니라 세 카드인가? 라이프사이클이 길어서입니다.

  • 계획은 점검 직후, 안전팀이 만듭니다.
  • 결과는 며칠~몇 주 뒤, 담당자가 적습니다.
  • 효과성은 또 몇 주 뒤, 작성자가 와서 평가합니다.

세 사람이, 세 시점에, 같은 EXE 의 다른 단계에 손을 댑니다. 한 카드로 묶으면 “내가 지금 만지면 안 되는 영역”이 시각적으로 안 잡힙니다. 카드를 쪼개니 각 단계 카드가 독립적으로 활성/비활성되고, 자기 단계가 아닌 사람은 옆에서 읽기만 하면 됩니다.

첨부파일까지 단계별로 컬럼이 분리됩니다.

schema
plan_attachment_file_paths           # 도면 / 시방서 / 기준
result_attachment_file_paths         # 실행 증빙 사진 / 보고서
effectiveness_attachment_file_paths  # 사후 측정 데이터 / 검증 사진

외부 협력업체 담당자는 결과 단계 첨부만 추가할 수 있습니다. 계획·효과성은 손도 못 댑니다. 이 가드는 캔버스의 카드 단위에서 자연스럽게 떨어집니다.

03 · The little details

작은 결정들이 캔버스를 살린다

캔버스를 만들고 한 달쯤 운영해 보니, 핵심은 “그래프를 그렸다”가 아니라 그 위에서 사람이 안 무서워야 한다는 거였습니다. 그래서 붙은 작은 장치들이 있습니다.

  • Undo 토스트 5초 — RCA / EXE 를 삭제해도 5초간 되돌리기 버튼이 떠 있습니다. 캔버스에서 노드는 무겁고, 실수로 지우면 회복이 어렵습니다.
  • 이탈 가드 — pending PATCH 가 있으면 beforeunload 가 잡아 새로고침을 막습니다. 캔버스는 작업 시간이 길어 도중 이탈이 흔합니다.
  • 노드별 댓글 패널 + 노드별 히스토리 패널— “이 원인에 대해 김 과장이 뭐라 했지?”, “이 조치 기한은 누가 언제 미뤘지?”가 그 카드 옆에서 바로 보입니다.
  • 낙관적 락(revision 컬럼) — 한 NC 를 여러 사람이 동시에 만져도 충돌하면 PATCH 가 거절됩니다. 캔버스는 자동저장 빈도가 높아 동시편집 충돌이 잦습니다.

04 · One canvas, two doors

같은 캔버스가 두 모달에 산다

캔버스 본문은 컴포넌트로 빼서, 두 진입 모달이 공유합니다.

  • 부적합 캔버스 — NC 한 건만 단독으로 펼쳐서 그립니다.
  • 활동 캔버스 — 좌측에 그 활동의 NC 리스트, 우측에 선택된 NC 의 캔버스. 점검 1회로 발견된 12건을 옆으로 휙휙 옮겨가며 작업합니다.

본문이 같다 보니 검토 행위가 한 가지로 통일됩니다 — “캔버스 위에서 노드를 만진다.” 어디서 들어왔든 같은 손동작입니다.

Next

다음 편 — “조치 완료”가 진짜 끝이 아닌 이유 — 효과성 평가와 재조치 사슬, 그리고 그게 NC 상태에 자동으로 반영되는 흐름.