프로젝트 목록

알스솔루션

BI 데이터 전처리 백오피스 구축

BI 서비스에 공개되는 약품 판매 데이터의 전처리를 담당하는 사내 백오피스. 데이터비즈팀이 Excel로 반려 데이터를 검토해 회신하고 담당자가 DB에 수기 입력하던 과정을, 웹 검수와 보정 접수로 대체. 범용 셸 + 제품 프로젝트 구조로 설계

기간: 2026.09 - 2026.10역할: 프론트엔드 개발 (설계·구현 주도)
React 19TypeScriptViteEmotionReact Query자체 디자인 시스템pnpm Monoreponode:testPlaywright

주요 성과

  • BI 서비스의 데이터 공급 측 — 전처리를 끝낸 보정 결과가 고객 공개를 거쳐 BI 화면의 분석 데이터가 된다
  • 문제: BI 서비스에 공개될 약품 판매 원천 데이터 중 검증에 실패한 '반려' 행을 데이터비즈팀이 일일 Excel 리포트(15시트)로 받아 검토·회신하고, 개발·운영 담당자가 그 결과를 DB에 손으로 옮겨 넣고 있었다. 팀이 직접 검수·판정·접수하고 그 결과가 배치·야간 처리·고객 공개까지 이어지는 과정을 한 화면에서 추적하도록 재설계
  • 업무 흐름을 단계로 분리: 현황 확인 → 반려 6개 탭 검수 → 초안 저장/보류 → 서버 미리보기 → 접수 → 실행 대기·보정 배치 → 고객 공개 → 감사 이력. 사람의 판정 · 규칙 저장 · 실제 실행 · 고객 공개를 서로 다른 것으로 다루고 각각 다르게 부름
  • Excel 손놀림을 그대로 받는 검수 표: 행별 직접 입력, 선택 항목 일괄 채우기, Excel 여러 행 붙여넣기, 행 상세 패널. 열 이름·단위·용어는 팀이 Excel에서 쓰던 말을 그대로 사용
  • 접수 안전성 설계: 미리보기 ID + payload 해시 + 멱등키를 함께 보내고 입력이 바뀌면 미리보기를 무효화. 멱등키는 범위별로 발급해 '접수된 뒤에만' 교체하고, 응답을 받지 못한 요청은 키·본문을 기억해 '다시 보내기'로만 재전송. 운영자가 입력을 바꾸면 그 요청을 버리고 '접수됐을 수 있다'고 알리며, 서버가 키 재사용을 거부(409)하면 그 범위의 키를 새로 발급
  • "결과를 모르는 실패"를 별도 상태로 취급: 게이트웨이 502·504·무응답·봉투 깨짐은 재시도시키지 않고 이력 확인부터 유도. 오류 케이스 474건(업무 영역 436 + 공통 38)을 코드에서 전수 수집해 거절 모달 · 확인 필요 모달 · 위험 토스트 · 인라인 네 자리로 분류
  • 빈 값을 0으로 적지 않는다: 확인 불가 · 준비 안 됨 · 미수신 · 기록 없음과 '값이 없는 것이 답'(—)을 구분하는 표시 규칙을 도메인 레이어에 상수로 고정. 금액·비율은 서버의 Decimal 문자열을 `Number()` 변환 없이 원문으로 표시
  • 규약을 문서가 아니라 시험으로 강제 — 규칙 시험 17종: SDK import 경계, 디자인 토큰 밖 리터럴, 단추 크기·확정 단추 variant, 표 마크업(날것 `<tr>`/`<td>` 금지), Emotion 선택자, 모달 제목 위계, 접근성 이름을 가질 수 없는 요소의 `aria-label`, 44px 터치 영역, 화면 문구 용어집·코드 라벨 대조까지 lint·테스트가 막음
  • 범용 셸 설계: 제품 프로젝트는 `defineProject`로 등록하고 메뉴·그룹·기본 화면·브레드크럼이 설정에서 파생. 제품마다 다른 셸 동작(도움말 가이드, 머리글 매출일자, 대기 건수 배지) 세 항목만 플래그로 켜서 범용 셸이 특정 제품의 화면 id·문구를 물려받지 않도록 분리. 레지스트리 누락·중복 메뉴·잘못된 기본 화면은 컴파일 오류
  • 앱과 공통 UI를 pnpm 모노레포로 분리하고 디자인 시스템이 업무 상태·API를 참조하지 않도록 단방향 의존 고정. Emotion 토큰으로 라이트·다크·리얼다크 3테마와 글꼴 4종·글자 크기 4단(12~18px)을 제공하고, 네온 강조색은 사용자가 고른 값이 본문 대비 기준(4.5:1)에 못 미치면 색조를 지킨 채 밝기만 올려 칠함
  • 세션·권한: 브라우저가 IdP에 직접 붙지 않는 BFF 세션 방식(직원 SSO), CSRF 헤더 없는 변경 차단. 메뉴 노출은 서버가 돌려준 역할·`capabilities`가 결정하고, 프런트엔드 메뉴 필터를 보안 경계로 쓰지 않음을 문서·코드 양쪽에 명시
  • 검증: node:test 252개 파일·약 1,766개 케이스 + 브라우저 E2E 6 spec(Playwright — 스모크·업무 흐름·초안·신원·셸·화면 설정). 응답을 받지 못한 쓰기 네 흐름은 화면을 그려 실제로 버튼을 누르는 수준까지 검사. `pnpm verify`가 포맷 → 디자인 시스템 빌드 → lint → 타입체크 → 전체 시험 → 앱 빌드 → 운영 번들에 예시 데이터가 섞였는지 검사까지 한 줄로 수행
  • 미검증 범위를 성과로 포장하지 않음: 실제 SSO 세션 검증은 개발 origin의 `redirect_uri` 미허용으로 아직 못 했고, 서버 권한·감사·멱등성과 예약 스케줄러·DAG 실행은 실환경에서 확인하지 않았음을 저장소 문서에 명시

설계 자세히 보기

Excel 왕복과 수기 입력으로 돌아가던 전처리를 웹으로 옮기면서, 가장 많이 고민한 건 화면 수가 아니라 “지금 무엇이 저장됐고 무엇이 실제로 실행됐는가”를 헷갈리지 않게 만드는 일이었다. 되돌리기 어려운 작업을 다루는 업무 도구라서, 모호함이 곧 사고였다.

15시트

대체한 일일 전처리 리포트 Excel

474건

코드에서 전수 수집한 오류 케이스

17종

규약을 막는 시험

~1,766케이스

node:test 252개 파일

1. 원래 어떻게 일하고 있었나

BI 서비스에 공개될 약품 판매 원천 데이터 중 검증에 실패한 행을 ‘반려’라고 부른다. 데이터비즈팀은 이 반려 데이터를 일일 Excel 리포트(15시트)로 받아 검토하고, 보정값을 정해 메일로 회신했다. 그러면 개발·운영 담당자가 그 내용을 DB에 손으로 옮겨 넣었다.

이 구조의 비용은 입력 시간이 아니라 추적 불가였다. 회신한 보정이 실제로 반영됐는지, 반영됐다면 고객 화면에 나갔는지를 팀이 알 방법이 없었다. 그래서 설계의 출발점을 “입력을 웹으로 옮기기”가 아니라 “단계를 눈에 보이게 쪼개기”로 잡았다.

저장 · 실행 · 공개를 섞지 않는다사람의 판정 — 되돌릴 수 있다실제 실행 — 되돌리기 어렵다1. 검수반려 6개 탭 · 직접 입력 · 일괄 채우기 · Excel 붙여넣기2. 초안 저장서버에 남긴다. 아직 아무것도 실행되지 않았다3. 서버 미리보기실재 여부·형식을 서버가 판정해 결과와 ID를 돌려준다4. 접수미리보기 ID + payload 해시 + 멱등키5. 보정 배치즉시 실행 또는 야간 처리. 대기·이력을 따로 본다6. 고객 공개공개 보류·일정을 거쳐 BI 화면의 데이터가 된다화면은 지금 어느 단계인지 항상 말한다 — 저장됐다와 실행됐다와 공개됐다는 다른 말이다

→ 익숙한 작업대는 그대로 뒀다. 팀이 Excel에서 쓰던 열 이름과 용어를 화면 문구로 쓰고, 행별 직접 입력·선택 항목 일괄 채우기·Excel 여러 행 붙여넣기처럼 하던 손놀림을 받는다. 바꾼 것은 도구가 아니라 단계의 경계다.

2. 접수 버튼을 두 번 눌렀을 때

접수는 배치와 고객 공개로 이어지는 되돌리기 어려운 작업이다. 그런데 게이트웨이 타임아웃처럼 서버가 받았는지 알 수 없는 실패가 실제로 난다. 여기서 흔한 처리는 “실패했습니다, 다시 시도”인데, 이미 접수된 요청이면 같은 보정이 두 번 들어간다.

그래서 실패를 두 종류로 나눴다. 서버가 거절한 실패는 다시 하면 되고, 결과를 모르는 실패는 다시 시도시키지 않고 이력에서 확인부터 시킨다. 재전송이 필요한 경우를 위해 멱등키를 범위별로 발급하고, 접수가 확인된 뒤에만 키를 바꾼다.

“실패”와 “결과를 모름”은 다른 것이다접수 요청미리보기 ID · 해시 · 멱등키접수됨그때만 키를 새로 발급결과를 모름 (502 · 504 · 무응답)키와 본문을 기억 → 주 단추가 '다시 보내기'로 바뀐다운영자가 입력을 바꿈그 요청을 버리고 '접수됐을 수 있다'고 한 줄 알린다서버가 키 재사용 거부 (409)그 범위의 키만 새로 발급 — 거절된 키는 다시 나가지 않는다결과를 모르는 요청은 재시도 단추를 주지 않는다 — 이력에서 확인부터 시킨다

→ 까다로운 건 세 번째 갈래다. 운영자가 응답을 기다리다 입력을 고치면, 화면의 값과 날아간 요청의 내용이 달라진다. 이때 새 입력으로 다시 보내면 둘 다 접수될 수 있다. 그래서 그 요청은 “버린 요청”으로 따로 남겨 접수됐을 가능성을 한 줄로 알리고, 새 입력은 새 키로만 나간다. 이 네 갈래와 “입력이 같아도 접수된 뒤에는 새 키” 같은 조건은 화면을 실제로 그려 버튼을 누르는 테스트로 고정했다.

3. 빈 값을 0으로 적지 않는다

데이터 검수 도구에서 0은 위험한 기본값이다. ‘수집했고 정상인데 0건’과 ‘아직 수집 기록이 없음’은 업무상 완전히 다른 판단으로 이어지는데, 널 값을 0으로 바꿔 그리면 화면에서 구별이 사라진다.

그래서 표시 규칙을 도메인 레이어의 상수로 고정했다. 확인 불가 · 준비 안 됨 · 미수신 · 기록 없음을 각각 다른 말로 쓰고, 값이 없는 것 자체가 답일 때만 ‘—’로 적는다. 이 규칙 밖의 새 표현을 만들지 않는 것까지 규약에 넣었다.

같은 이유로 금액·비율은 서버가 주는 10진 문자열을 숫자로 변환하지 않고 원문 그대로 표시한다. 정렬에만 숫자 비교를 쓰고, 보여주기·복사·상세는 문자열이다 — 부동소수 변환으로 끝자리가 바뀌면 그건 틀린 매출액이다.

4. 오류가 어디에 뜨는지를 먼저 정했다

업무 도구에서 오류 표시는 장식이 아니라 다음 행동을 지시하는 장치다. 그런데 화면을 늘리다 보면 같은 성질의 실패가 어떤 화면에서는 모달, 어떤 화면에서는 토스트로 뜬다. 그래서 코드 전체에서 오류가 날 수 있는 지점을 474건(업무 영역 436 + 공통 38) 수집해 네 자리로 분류했다.

거절 모달

권한 없음 · 충돌 · 대상이 사라짐

서버가 "안 된다"고 확정한 것 — 입력을 고쳐야 한다

확인 필요 모달

5xx · 무응답 · 응답 봉투 깨짐

결과를 모른다 — 다시 하지 말고 이력을 보라는 뜻

위험 토스트

요청 형식 · 용량 · 빈도 · 일시 중단

흐름을 끊을 필요는 없지만 눈에 띄어야 하는 것

인라인 문구만

목록·상세 읽기 실패, 단계 중간 검증

표 자리에서 바로 재시도하면 되는 것 — 모달로 덮지 않는다

5. 규약 17종을 시험으로 바꿨다

사람 리뷰어가 없는 환경에서 “이렇게 하기로 했다”는 문서는 안 지켜진다. 특히 디자인 일관성·접근성·문구처럼 깨져도 동작은 하는 규칙이 먼저 무너진다. 그래서 지켜야 할 것을 실패하는 시험으로 옮겼다.

SDK import 경계

운영 데이터 호출을 데이터 레이어 밖으로 새지 않게

디자인 토큰 리터럴

척도 밖 값은 같은 줄에 이유를 달아야 통과

단추 크기·확정 변형

되돌릴 수 없는 전이와 다시 고칠 수 있는 저장을 다른 색으로

표 마크업

날것 <tr>·<td>와 표 내부 CSS 덮어쓰기 금지

Emotion 선택자

:has(·:is( 시작 금지 — 부모 클래스에 붙어 범위가 뒤집힌다

모달 제목 위계

창 제목이 h2이므로 안쪽 구역은 h3부터

접근성 이름

이름을 가질 수 없는 요소에 aria-label 금지

터치 영역

작은 조작 요소도 44px까지 넓어지는지

문구 용어집 대조

코드의 라벨 표와 문구 문서의 행이 값까지 일치하는지

→ 가장 효과가 컸던 건 문구 시험이다. 코드의 라벨 표와 문구 문서의 용어집을 값까지 대조하므로, 화면에서 용어를 바꾸면 문서를 같이 고치지 않는 한 시험이 깨진다. 업무 도구에서 용어 불일치는 오해를 만들고, 오해는 잘못된 보정으로 이어진다.

6. 셸은 제품을 모른다

지금 들어있는 제품은 전처리 프로젝트 하나와 플랫폼 관리지만, 앞으로 다른 제품이 같은 셸에 들어온다. 이럴 때 흔한 실패가 범용 셸이 첫 제품의 화면 id와 문구를 물려받는 것이다.

그래서 제품을 설정 객체로 등록하고 메뉴·그룹·기본 화면·브레드크럼이 전부 거기서 파생되게 했다. 제품마다 다른 셸 동작은 세 항목(도움말 가이드 종류, 머리글에 매출일자를 쓸지, 대기 건수 배지를 붙일 화면)으로 만 열어두고, 없으면 켜지 않는다. 레지스트리에 빠뜨리거나 메뉴에 없는 화면을 기본값으로 두면 컴파일 오류가 난다.

→ 이 설정은 화면 탐색용이고 권한이 아니다. 메뉴 노출은 서버가 돌려준 역할과 권한 목록이 정하며, 각 업무 API의 인가는 서버가 다시 검사한다. 프런트엔드 메뉴 필터를 보안 경계로 쓰지 않는다는 것을 코드 주석과 저장소 문서 양쪽에 남겼다 — 실제로 브라우저에 메뉴 ON/OFF를 저장하던 화면이 있었고, 그게 보안 경계처럼 읽힐 수 있어 지웠다.

아직 검증하지 못한 것

실제 직원 SSO 세션으로는 아직 한 번도 열어보지 못했다. 개발 환경 주소가 인증 서버의 콜백 허용 목록에 올라가지 않아서다. 프런트엔드 설정이 준비된 것과 운영 적용이 끝난 것은 다른 일이라, 저장소 문서에 그대로 적어 뒀다.

서버 쪽 권한·감사·멱등성 보장과 예약 스케줄러·배치 실행도 실환경에서 확인하지 않았다. 현재 검증은 배포 명세 기반 예시 데이터와 헤드리스 브라우저 범위다. 성과를 말할 때 이 선을 넘지 않으려고 범위를 문서에 먼저 고정했다.