표가 아니라 캔버스로 — 부적합을 노드 그래프로 그린 이유
1 NC → N RCA → N EXE 의 트리는 폼/리스트 UI 로 한 화면에 안 들어옵니다. NC·RCA·EXE 각각을 캔버스 노드로 두고 좌표를 데이터에 박은 결정, 그리고 EXE 한 row 가 노드 셋으로 쪼개진 이유.
2026년 7월 12일 · 정윤환 · 6분
부적합 관리 화면을 만들면 보통 이렇게 됩니다.
- NC 리스트 (테이블)
- NC 행 클릭 → 상세 페이지
- 상세 페이지 안에 RCA 탭, EXE 탭
- 각 탭 안에 또 리스트 → “+ 추가” 모달
클릭이 깊습니다. 그리고 결정적으로, 트리가 안 보입니다. 부적합 하나에 원인이 셋이고, 그중 첫째 원인에 조치가 둘인데 둘 다 효과성 평가가 미정이라는 구조가, 폼/리스트 UI 로는 한 화면에 안 들어옵니다.
form & list
- 탭 → 모달 → 탭
- 1 NC → N RCA → N EXE 가 한 화면에 안 들어옴
- 의미 묶음(사람 vs 설비)이 사라짐
canvas
- 같은 캔버스 위에 노드
- 좌표가 데이터에 박혀 매번 같이 복원
- 사용자가 의도한 묶음이 살아남음
01 · Our pick
우리 선택 — 캔버스
NC, RCA, EXE 각각을 캔버스 위 노드로 두었습니다. 사용자가 카드를 끌어다 자유롭게 배치하고, 카드끼리 선으로 연결합니다.
데이터 모델에 박힌 흔적은 단순합니다.
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 라도 캔버스에서는 보통 계획 노드 / 결과 노드 / 효과성 노드 세 카드로 나뉘어 보입니다. 그래서 EXE 에 canvas_x/y + result_canvas_x/y + effectiveness_canvas_x/y 세 쌍의 좌표가 있어요.
왜 한 카드가 아니라 세 카드인가? 라이프사이클이 길어서입니다.
- 계획은 점검 직후, 안전팀이 만듭니다.
- 결과는 며칠~몇 주 뒤, 담당자가 적습니다.
- 효과성은 또 몇 주 뒤, 작성자가 와서 평가합니다.
세 사람이, 세 시점에, 같은 EXE 의 다른 단계에 손을 댑니다. 한 카드로 묶으면 “내가 지금 만지면 안 되는 영역”이 시각적으로 안 잡힙니다. 카드를 쪼개니 각 단계 카드가 독립적으로 활성/비활성되고, 자기 단계가 아닌 사람은 옆에서 읽기만 하면 됩니다.
첨부파일까지 단계별로 컬럼이 분리됩니다.
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 상태에 자동으로 반영되는 흐름.