버전, 미리보기, 메타데이터, 명확한 상태 관리를 위한 실용적인 데이터 모델과 UI 패턴으로 문서 중심 워크플로우를 설명합니다.

문서 자체가 사용자가 만들고 검토하며 의존하는 핵심 제품일 때, 그 앱을 문서 중심(document-centric)이라고 부릅니다. 사용자 경험은 PDF, 이미지, 스캔, 영수증 같은 파일을 중심으로 설계되고, 파일이 단순 첨부물로 취급되는 폼 중심 앱과는 다릅니다.
문서 중심 워크플로우에서는 사용자가 문서 안에서 실제 작업을 합니다: 열어보고, 무엇이 바뀌었는지 확인하고, 맥락을 추가하고, 다음 행동을 결정합니다. 문서를 신뢰할 수 없으면 앱의 유용성은 곧 사라집니다.
대부분의 문서 중심 앱은 초기에 몇 가지 핵심 화면이 필요합니다:
문제는 금세 드러납니다. 사용자가 같은 영수증을 두 번 업로드합니다. 누군가 PDF를 편집하고 이유를 설명하지 않고 다시 올립니다. 스캔에 날짜도, 상점도, 소유자도 없습니다. 몇 주 뒤에는 어느 버전이 승인되었는지, 어떤 근거로 결정했는지 아무도 모릅니다.
좋은 문서 중심 앱은 빠르고 믿을 수 있게 느껴집니다. 사용자는 몇 초 안에 다음 질문들에 답할 수 있어야 합니다:
그 명확성은 정의에서 옵니다. 화면을 만들기 전에 앱에서 “버전”, “미리보기”, “메타데이터”, “상태”가 무엇을 의미하는지 결정하세요. 이 용어들이 모호하면 중복, 혼란스러운 히스토리, 실제 작업과 맞지 않는 검토 플로우가 생깁니다.
UI는 간단해 보일 수 있지만(목록, 뷰어, 몇 개 버튼) 데이터 모델이 모든 무게를 견딥니다. 핵심 객체가 올바르면 감사 이력, 빠른 미리보기, 신뢰할 수 있는 승인 흐름이 훨씬 쉬워집니다.
먼저 “문서 레코드”와 “파일 콘텐츠”를 분리하세요. 레코드는 사용자가 말하는 대상입니다(ACME 청구서, 택시 영수증). 콘텐츠는 교체되거나 재처리되거나 이동될 수 있는 바이트(PDF, JPG)입니다. 내부 의미는 바뀌지 않습니다.
실용적인 객체 집합 예:
ID가 절대 변하지 않도록 무엇에 ID를 부여할지 결정하세요. 유용한 규칙은: Document ID는 영원히 유지하고, File과 Preview는 재생성 가능하게 두세요. Versions도 안정적인 ID가 필요합니다. 사람들이 “어제 모습”을 참조하므로 감사 추적에 필요합니다.
관계를 명시적으로 모델링하세요. 하나의 Document는 여러 Version을 가질 수 있습니다. 각 Version은 서로 다른 크기나 형식의 여러 Preview를 가질 수 있습니다. 이렇게 하면 목록 화면은 경량 미리보기 데이터만 로드해 빠르게 나타낼 수 있고, 상세 화면은 필요할 때만 전체 파일을 불러옵니다.
예: 사용자가 구겨진 영수증 사진을 업로드합니다. Document를 생성하고 원본 File을 저장하고 썸네일 Preview를 생성하고 Version 1을 만듭니다. 나중에 더 선명한 스캔을 올리면 Version 2가 되며, 댓글·승인·검색은 Document에 연결된 상태로 유지됩니다.
사람들은 문서가 시간이 지나며 바뀌더라도 "다른 항목으로 변해버리길" 기대하지 않습니다. 가장 간단한 해결책은 정체성(Document)과 콘텐츠(Version·File)를 분리하는 것입니다.
항상 변하지 않는 document_id로 시작하세요. 사용자가 같은 PDF를 다시 올리거나 흐릿한 사진을 교체하거나 수정된 스캔을 업로드해도 같은 문서 레코드로 남아야 합니다. 댓글·할당·감사 로그는 하나의 durable ID에 깔끔하게 붙습니다.
의미 있는 변경은 모두 새로운 version 행으로 처리하세요. 각 버전은 누가 언제 만들었는지, 저장 포인터(파일 키, 체크섬, 크기, 페이지 수), 해당 파일에 묶인 파생 산출물(OCR 텍스트, 미리보기 이미지 등)을 캡처해야 합니다. "제자리 편집"은 처음엔 단순해 보이지만 추적성을 망가뜨리고 버그를 되돌리기 어렵게 만듭니다.
빠른 조회를 위해 문서에 current_version_id를 유지하세요. 대부분의 화면은 "최신"만 필요하므로 매번 버전을 정렬할 필요가 없습니다. 히스토리가 필요하면 버전을 별도로 로드해 깨끗한 타임라인으로 보여주면 됩니다.
롤백은 포인터 변경에 불과합니다. 무엇인가를 삭제하는 대신 current_version_id를 이전 버전으로 설정하세요. 빠르고 안전하며 감사 추적도 보존됩니다.
히스토리를 이해하기 쉽게 유지하려면 각 버전이 왜 존재하는지 기록하세요. 작고 일관된 reason 필드(옵션 노트 포함)는 이유 모를 업데이트로 가득한 타임라인을 막아줍니다. 일반적인 이유: 재업로드 교체, 스캔 정리, OCR 수정, 가리기(레다케이션), 승인 편집 등.
예: 재무팀이 영수증 사진을 업로드하고 더 선명한 스캔으로 교체한 뒤 OCR을 고쳐 합계가 읽히게 합니다. 각 단계는 새로운 버전이지만 문서는 인박스에서 하나의 항목으로 유지됩니다. OCR 수정이 잘못되었다면 current_version_id만 클릭 한 번으로 이전으로 돌리면 됩니다.
문서 중심 워크플로우에서 미리보기는 사용자가 가장 많이 상호작용하는 요소입니다. 미리보기가 느리거나 불안정하면 앱 전체가 망가진 것처럼 느껴집니다.
미리보기 생성을 업로드 화면이 기다리는 동작으로 처리하지 말고 별도의 작업으로 취급하세요. 원본 파일을 먼저 저장하고 사용자에게 제어를 돌려준 뒤 백그라운드에서 미리보기를 생성하세요. 이렇게 하면 UI 응답성이 유지되고 재시도도 안전합니다.
여러 크기의 미리보기를 저장하세요. 하나의 크기는 모든 화면에 맞지 않습니다: 목록용 작은 썸네일, 분할보기용 중간 이미지, 상세 검토용 전체 페이지 이미지(PDF의 경우 페이지별) 등이 필요합니다.
미리보기 상태를 명시적으로 추적해 UI가 항상 무엇을 보여야 할지 알게 하세요: pending, ready, failed, needs_retry. UI에는 친숙한 라벨을 사용하되 데이터에는 명확한 상태를 유지하세요.
빠른 렌더링을 위해 파생값을 매번 계산하지 말고 Preview 레코드와 함께 캐시하세요. 일반 필드는 페이지 수, 미리보기 너비/높이, 회전(0/90/180/270), 추천 썸네일 페이지 등입니다.
느리고 문제 있는 파일을 대비해 설계하세요. 200페이지 스캔 PDF나 구겨진 영수증 사진은 처리에 시간이 걸릴 수 있습니다. 점진적 로딩을 사용해 준비된 첫 페이지를 바로 보여주고 나머지를 채워 넣으세요.
예: 사용자가 영수증 사진 30장을 업로드합니다. 목록 보기에는 썸네일이 "pending"으로 표시되고, 각 카드가 미리보기 완료 시 "ready"로 바뀝니다. 일부 이미지가 손상되어 실패하면 전체 배치를 차단하지 않고 깔끔한 재시도 버튼과 함께 실패 상태로 남깁니다.
메타데이터는 파일 더미를 검색·정렬·검토·승인 가능한 자료로 바꿉니다. 사람들은 간단한 질문에 빠르게 답하길 원합니다: 이 문서는 무엇인가? 누가 보냈나? 유효한가? 다음에 무엇을 해야 하나?
메타데이터를 깔끔하게 유지하는 실용적 방법은 출처별로 분리하는 것입니다:
이 버킷들은 나중에 분쟁을 막아줍니다. 합계가 틀리다면 OCR에서 왔는지 사람이 수정했는지 확인할 수 있습니다.
영수증·송장에는 깔끔한 필드 집합을 일관되게 사용하면 큰 이득입니다(동일한 이름, 동일한 형식). 흔한 핵심 필드는 vendor, date, total, currency, document_number입니다. 처음엔 필수로 강제하지 말고 선택 항목으로 두세요. 사람들은 부분 스캔이나 흐릿한 사진을 올리고, 한 필드 때문에 진행을 막으면 워크플로우가 느려집니다.
알 수 없는 값도 1등 시민처럼 다루세요. null/unknown 같은 명시적 상태와 이유(페이지 누락, 읽을 수 없음, 해당 없음)를 함께 저장하면 문서를 진행시키면서도 검토자에게 무엇이 문제인지 보여줄 수 있습니다.
추출된 필드에는 출처와 신뢰도도 저장하세요. 출처는 user, OCR, import, API 등이 될 수 있고, 신뢰도는 0-1 점수나 high/medium/low 같은 소규모 집합으로 표현할 수 있습니다. OCR이 "$18.70"을 낮은 신뢰도로 읽었다면 UI가 이를 강조해 빠른 확인을 요청할 수 있습니다.
다중 페이지 문서에는 어떤 값이 문서 전체에 속하는지와 개별 페이지에 속하는지를 결정해야 합니다. 합계나 상점 정보는 일반적으로 문서 전체에 해당합니다. 페이지별 노트, 가리기, 회전, 페이지 분류는 페이지 레벨에 두는 것이 맞습니다.
상태는 한 가지 질문에 답합니다: "이 문서는 현재 프로세스에서 어디에 있는가?" 상태는 작고 단순하게 유지하세요. 누군가 요청할 때마다 상태를 추가하면 아무도 신뢰하지 않는 필터가 생깁니다.
실제 결정과 매핑되는 실용적인 비즈니스 상태 집합:
"처리 중(processing)"은 비즈니스 상태에 넣지 마세요. OCR 실행이나 미리보기 생성은 시스템이 무엇을 하는지를 설명하는 것이지, 사람이 다음에 무엇을 해야 하는지를 설명하지 않습니다. 이런 것들은 별도의 처리 상태로 저장하세요.
할당(assignee_id, team_id, due_date)도 상태와 분리하세요. 문서는 승인됨 상태라도 후속 작업을 위해 여전히 할당될 수 있고, 검토 필요 상태라도 담당자가 지정되지 않았을 수 있습니다.
상태 이력을 기록하세요. (from_status, to_status, changed_at, changed_by, reason) 같은 간단한 로그는 "누가 이 영수증을 거부했고 그 이유는 무엇인가?"라는 질문에 답할 때 유용합니다.
마지막으로 각 상태에서 허용되는 액션을 결정하세요. 규칙은 단순하게: Imported는 Needs review로 이동 가능; Approved는 새 버전이 생성될 때까지 읽기 전용; Rejected는 재오픈 가능하지만 이전 이유는 유지해야 합니다.
대부분의 시간은 목록을 훑고, 한 항목을 열어 몇 개 필드를 고치고, 다음 항목으로 넘어가는 데 쓰입니다. 좋은 UI는 그 단계를 빠르고 예측 가능하게 만듭니다.
문서 목록에서는 각 행을 요약 카드처럼 다뤄 사용자가 모든 파일을 열지 않고도 결정할 수 있게 하세요. 강한 행 구성은 작은 썸네일, 명확한 제목, 핵심 필드(상점, 날짜, 합계), 상태 배지, 주의가 필요한 경우 미묘한 경고를 보여줍니다.
상세 뷰는 차분하고 스캔하기 쉬워야 합니다. 일반적인 레이아웃은 왼쪽에 미리보기, 오른쪽에 메타데이터이며 각 필드 옆에 편집 컨트롤을 둡니다. 사용자는 확대/축소, 회전, 페이지 넘기기를 하면서도 폼에서 자신의 위치를 잃지 않아야 합니다. 추출된 필드라면 신뢰도 힌트를 작게 보여주고, 필드에 포커스하면 미리보기에서 해당 영역을 강조하면 좋습니다.
버전은 드롭다운보다 타임라인으로 볼 때 더 잘 작동합니다. 누가 언제 무엇을 바꿨는지 보여주고, 과거 버전을 읽기 전용으로 열 수 있게 하세요. 비교 기능을 제공한다면 픽셀 단위 PDF 비교보다는 메타데이터 차이(금액 변경, 상점 수정)에 집중하세요.
검토 모드는 속도를 최적화해야 합니다. 키보드 중심의 분류 흐름으로 빠른 승인/거부, 자주 고치는 필드에 대한 빠른 수정, 거부 사유를 위한 짧은 코멘트 박스로 충분한 경우가 많습니다.
빈 상태(empty state)도 중요합니다. 문서는 종종 처리 중이므로 빈 박스 대신 “미리보기 생성 중”, “OCR 실행 중”, “이 파일 형식은 미리보기가 없습니다” 같은 메시지를 보여주세요.
단순한 흐름은 '업로드 → 확인 → 승인'처럼 느껴져야 합니다. 내부적으로는 파일(버전·미리보기)과 비즈니스 의미(메타데이터·상태)를 분리하면 가장 잘 동작합니다.
사용자가 PDF, 사진, 영수증 스캔을 업로드하면 즉시 인박스 목록에 보이게 하세요. 처리 완료를 기다리지 마세요. 파일명, 업로드 시간, "Processing" 같은 명확한 배지를 보여줍니다. 이미 소스(이메일 수집, 모바일 카메라, 드래그앤드롭)를 알고 있다면 그것도 표시하세요.
업로드 시 Document 레코드(장수명)와 Version 레코드(이 특정 파일)를 생성하세요. current_version_id를 새 버전으로 설정합니다. preview_state = pending, extraction_state = pending으로 두어 UI가 무엇이 준비됐는지 정직하게 표시하게 하세요.
상세 뷰는 즉시 열리되, 자리 표시자 뷰어와 "미리보기 준비 중" 메시지를 보여주어 깨진 프레임 대신 명확한 상태를 알리세요.
백그라운드 작업이 썸네일과 보기 가능한 미리보기(페이지 이미지 또는 리사이즈된 사진)를 생성합니다. 다른 작업이 메타데이터(상점, 날짜, 합계, 통화, 문서 유형)를 추출합니다. 각 작업이 끝날 때마다 해당 상태와 타임스탬프만 업데이트해 실패를 재시도해도 다른 부분을 건드리지 않게 하세요.
UI는 간결해야 합니다: 미리보기 상태, 데이터 상태를 보여주고 낮은 신뢰도의 필드를 강조하세요.
미리보기가 준비되면 검토자가 필드를 수정하고 노트를 추가하며 문서를 비즈니스 상태(수집됨 -> 검토 필요 -> 승인됨 또는 거부됨)로 이동시킵니다. 누가 무엇을 언제 변경했는지 기록하세요.
검토자가 수정된 파일을 업로드하면 새 Version이 되고 문서는 자동으로 검토 필요 상태로 돌아갑니다.
내보내기, 회계 동기화, 내부 리포트는 current_version_id와 승인된 메타데이터 스냅샷을 읽어야 합니다. "최신 추출"에서 직접 읽으면 반쪽 처리된 재업로드가 숫자를 바꿔 버릴 수 있습니다.
문서 중심 워크플로우는 자주 지루한 이유로 실패합니다: 초기에 취한 지름길이 매일의 골칫거리로 커집니다. 사람들이 중복 업로드를 하거나 실수를 고치거나 “누가 언제 이걸 바꿨지?”를 물을 때 문제가 됩니다.
파일명을 문서의 정체성으로 취급하는 것은 고전적 실수입니다. 이름은 변하고, 사용자는 다시 업로드하고, 카메라는 IMG_0001 같은 중복을 만듭니다. 각 문서에 안정적인 ID를 부여하고 파일명은 레이블로 취급하세요.
교체 시 원본 파일을 덮어쓰는 것도 문제를 일으킵니다. 간단해 보이지만 감사 추적이 사라지고 나중에 어떤 것이 승인되었는지, 무엇이 편집되었는지, 무엇이 전송되었는지를 답할 수 없게 됩니다. 바이너리 파일은 불변으로 유지하고 새 버전 레코드를 추가하세요.
상태 혼동은 미묘한 버그를 만듭니다. "OCR 실행 중"은 "검토 필요"와 같지 않습니다. 처리 상태는 시스템이 하는 일이고 비즈니스 상태는 사람이 해야 할 일을 설명합니다. 이 둘이 섞이면 문서가 잘못된 버킷에 갇힐 수 있습니다.
UI 결정도 마찰을 만들 수 있습니다. 미리보기가 생성될 때까지 화면을 막으면 업로드가 성공했음에도 앱이 느리게 느껴집니다. 문서를 즉시 보여주고 유용한 자리 표시자를 둔 다음 준비되면 썸네일을 교체하세요.
마지막으로 메타데이터는 출처 없이 값을 저장하면 신뢰할 수 없게 됩니다. 합계가 OCR에서 나왔다면 그렇게 표시하세요. 타임스탬프를 유지하세요.
간단한 체크리스트:
예: 영수증 앱에서 사용자가 더 선명한 사진을 재업로드하면 버전화하고 이전 이미지는 보관하며 OCR 재처리를 표시하고 검토가 완료될 때까지 검토 필요 상태를 유지하세요.
문서 중심 워크플로우는 사람들이 본 것을 신뢰하고 문제가 생겼을 때 복구할 수 있을 때만 "완성"으로 느껴집니다. 출시 전에 흐릿한 영수증, 회전된 PDF, 반복 업로드 같은 지저분한 실제 문서로 테스트하세요.
거의 모든 놀라움을 잡아내는 다섯 가지 점검:
간단한 현실 테스트: 누군가에게 비슷한 영수증 세 장을 검토하게 하고 일부러 하나를 잘못 바꾸게 하세요. 그들이 활성 버전을 식별하고 상태를 이해하며 1분 이내에 실수를 고칠 수 있다면 거의 준비된 상태입니다.
월별 영수증 환급은 문서 중심 작업의 명확한 예입니다. 직원이 영수증을 업로드하고 두 명의 검토자(매니저, 재무)가 확인합니다. 영수증이 제품이므로 앱은 버전 관리, 미리보기, 메타데이터, 명확한 상태에 달려 있습니다.
예: Jamie가 택시 영수증 사진을 업로드하면 시스템은 Document #1842와 Version v1(원본 파일), 썸네일과 미리보기, merchant/date/currency/total 같은 메타데이터와 OCR 신뢰도 점수를 생성합니다. 문서는 Imported에서 미리보기와 추출이 준비되면 Needs review로 이동합니다.
나중에 Jamie가 같은 영수증을 다시 업로드하면 중복 검사(파일 해시 + 유사한 상점/날짜/합계)가 "#1842와 중복 가능"을 제안합니다. 사용자가 연결을 선택하면 같은 Document에 또 다른 File로 저장해 하나의 검토 스레드와 상태를 유지할 수 있습니다.
검토 중 매니저는 미리보기, 핵심 필드, 경고를 봅니다. OCR이 합계를 $18.00으로 추정했지만 이미지에는 $13.00이 보입니다. Jamie가 합계를 수정하면 히스토리를 덮어쓰지 마세요. Version v2를 만들고 v1은 그대로 두며 "Jamie가 합계를 수정"이라고 기록하세요.
이런 워크플로우를 빠르게 구축하고 싶다면 Koder.ai (koder.ai)가 채팅 기반 플랜에서 첫 동작 버전을 생성하는 데 도움을 줄 수 있지만, 동일한 규칙이 적용됩니다: 먼저 객체와 상태를 정의하고 그다음 화면을 만드세요.
실무적 다음 단계:
문서를 부수적인 첨부물이 아니라 사용자가 실제로 작업하는 중심 대상으로 보는 앱입니다. 사용자는 문서를 열어 신뢰성을 확인하고, 어떤 부분이 바뀌었는지 파악한 뒤 그 문서를 바탕으로 다음 행동을 결정해야 합니다.
인박스/목록, 빠른 미리보기가 있는 문서 상세 뷰, 간단한 검토 액션(승인/거부/수정 요청), 그리고 내보내기/공유 기능을 먼저 만드세요. 이 네 화면이 찾기 → 열기 → 결정 → 전달의 일반적인 루프를 커버합니다.
변하지 않는 Document 레코드를 만들고, 실제 파일 바이트는 별도의 File 객체로 저장하세요. 특정 파일과 파생 산출물을 묶는 스냅샷 역할로 Version을 추가하면 재업로드가 와도 댓글·할당·히스토리가 깨지지 않습니다.
의미 있는 변경은 항상 새 버전으로 만드세요. 제자리 편집(edit in place)은 추적을 망가뜨립니다. 문서에는 current_version_id를 두어 대부분의 화면은 최신만 빠르게 읽게 하고, 감사 또는 롤백이 필요하면 버전 타임라인을 조회하세요.
원본 파일을 먼저 저장하고, 미리보기 생성은 백그라운드 작업으로 처리하세요. 업로드가 즉시 끝난 것처럼 보이게 하고, pending/ready/failed 같은 상태를 추적해 UI가 정확히 무엇을 보여야 하는지 알게 하세요. 크기별로 여러 미리보기를 저장하면 목록과 상세화면 모두 빠르게 렌더됩니다.
메타데이터를 세 가지 버킷으로 나누세요: 시스템(파일명/크기/타입), 추출( OCR 텍스트·감지된 필드·바코드 ), 사용자 입력(수정·태그·노트). 각 값의 출처를 함께 저장하면 나중에 누가/어떻게 값을 만들었는지 알 수 있어 신뢰성이 유지됩니다.
사람이 다음에 무엇을 해야 하는지를 설명하는 소수의 비즈니스 상태만 사용하세요. 예: 수집됨(Imported), 검토 필요(Needs review), 승인됨(Approved), 거부됨(Rejected), 보관됨(Archived). 처리 중(예: OCR 실행)은 별도의 처리 상태에 두어야 문서가 잘못된 버킷에 갇히지 않습니다.
업로드 시 불변 체크섬을 저장해 비교하고, 가능한 경우 상점/날짜/총액 같은 핵심 필드를 같이 검사하세요. 중복으로 의심되면 “#1842와 유사합니다. 연결할까요 아니면 폐기할까요?” 같은 명확한 선택을 제공해 검토 스레드를 분절시키지 마세요.
누가, 언제, 왜 상태를 바꿨는지 기록하는 상태 이력 로그를 유지하세요(예: from_status, to_status, changed_at, changed_by, reason). 롤백은 항목을 삭제하지 않고 current_version_id 포인터만 이전 버전으로 되돌리는 방식이면 빠르고 안전합니다.
먼저 객체와 상태를 정의하세요( Document/Version/File 등 ). 그런 다음 채팅 플랜으로 앱을 생성하면 Koder.ai가 초안을 빠르게 만들어 줄 수 있습니다. 생성 전에 추적 가능한 모델을 명확히 해 두면 자동 생성된 화면도 실제 워크플로우에 맞게 동작합니다.