Posts
엑셀 변환 스크립트 하나가 운영 자동화 도구가 되기까지

엑셀 변환 스크립트 하나가 운영 자동화 도구가 되기까지

매주 8시간짜리 엑셀 변환 작업에서 시작해 xl3와 Exform으로 운영 공수를 줄이기까지의 프로젝트 히스토리

올해 초에 엑셀 파일을 하나 받았다. 스낵 식자재 발주서를 우리 시스템에 등록할 수 있게 포맷을 바꿔달라는 요청이었다.

별일 아니겠거니 하고 열었다가 48기가 메모리 맥북에서 엑셀이 뻗는 걸 처음 봤다.

"엑셀 함수나 매크로 같은 순수 기능을 쓰면 안 되나?" = 오산.

애초에 열 수조차 없는 파일이었다. 담당자는 이걸 매주 8시간씩 수기로 만지고 있었다. 엑셀을 여는 것부터 이미 업무가 아니라 전투에 가까웠다.

매주 8시간 -> 1초컷

처음에는 간단한 변환 스크립트를 만들어주면 끝날 일이라고 생각했다. AI 덕분에 코드 작성 비용은 거의 없어졌고, 이런 서브 프로젝트는 코드 퀄리티를 아주 빡세게 따질 일도 아니니 빠르게 만들어주면 된다.

그런데 브라우저도 뻗었다.

엑셀은 못 열고, 브라우저도 못 버티고, 사람이 매주 8시간씩 하고 있고.

결국 Rust로 변환 로직을 짜고 WASM으로 붙였다. 결과는 0.2초 근처. 설명하기 쉽게 말하면 매주 8시간짜리 작업이 1초컷이 됐다.

달달했다.

이때만 해도 이 방식이 답인 줄 알았다. 어려운 엑셀 업무가 보이면 개발자가 가서 스크립트를 짜준다. 실무자는 편해지고 개발자는 효능감을 얻는다. 깔끔하잖아.

근데 이건 한 건을 잘 처리한 것이지, 구조를 바꾼 건 아니었다.

100곳은 못 도와준다

시간이 조금 지나고 실무자들의 운영 공수를 덜어주는 AX TF에 정식으로 합류했다. 거의 모든 팀이 엑셀을 다뤘고, 그중에서도 운영컨설팅팀의 거래명세서 변환 업무가 꽤 큰 덩어리였다.

매달 정산 시즌이 되면 담당자가 고객사들에게 거래명세서를 발급한다. 우리 서비스는 스낵24, 커피24, 조식24처럼 종류가 다양하고, 서비스가 다양하니 고객사들이 원하는 양식도 제각각이다.

대부분은 시스템에서 내려주는 기본 양식으로 충분하지만 끝까지 자사 포맷을 고수하는 거래처가 있다. 문제는 그 수가 적지 않다는 것.

당시 정리한 기준으로는 전체 관리 기업이 190개였고, 그중 77개 기업은 수기 변환 대상이었다. 2026년 2월 정산 기준으로 2명이 영업일 약 5일을 이 작업에 썼다.

이전 달에 보낸 파일을 열고, 이번 달 원본을 열고, 컬럼 순서를 맞추고, 항목 이름을 바꾸고, 특정 거래처는 요약 시트를 만들고, 어떤 곳은 파일명까지 규칙을 맞춘다. 숫자는 틀리면 안 되고, 양식도 틀리면 안 된다.

연초의 성공 공식을 그대로 가져오면 어떻게 될까?

거래처 77곳의 변환 스크립트를 개발자가 하나하나 만들어줘야 한다. 조금 넓게 보면 100곳 가까운 커스텀 엑셀 변환 로직을 개발팀이 계속 들고 가는 구조가 된다.

  • 매번 실무자의 변환 메커니즘을 개발자가 이해해야 함
  • 규칙이 바뀌면 유지보수도 개발자 몫
  • 작은 양식 변경도 코드 수정/배포/검증 흐름을 탐
  • 실무자가 LLM으로 스크립트를 직접 뽑아도 시스템화는 결국 개발자가 해줘야 함
  • 담당자가 바뀌면 "왜 이렇게 변환하는지"가 같이 사라짐

한 곳 도와줄 때마다 달달함은 있는데 문제의 총량은 안 줄어든다.

문제를 다시 정의해야 했다.

변환 코드가 없는 게 문제가 아니라, 변환 규칙이 담당자 머릿속에만 있는 게 문제였다. 거래처가 늘수록 사람도 늘어야 하는 구조가 문제였다.

jxls, 그리고 붕어빵 틀

단서는 의외로 가까이 있었다. 우리 시스템의 엑셀 다운로드 기능은 jxls 기반이다.

템플릿 엑셀 파일에 특수 문법을 적어두면, 시스템이 데이터를 바인딩해서 결과 엑셀을 만들어준다.

이 구조를 보다가 붕어빵 가게가 떠올랐다.

밀가루 반죽 -> 붕어빵 틀 -> 붕어빵

틀이 있으면 사장님이 바뀌어도 가게가 돌아간다. jxls의 템플릿 파일이 바로 그 틀이다. 변환 규칙이 사람 머릿속이나 코드 어딘가가 아니라 파일 안에 있다.

그동안 내가 한 일은 반대였다. 틀 없는 가게에 가서 붕어를 대신 빚어주고 온 것.

근데 jxls 템플릿은 개발자가 만든다. 실무자에게 <jx:forEach items="..."> 같은 문법을 보여주면서 "이제 직접 고치시면 됩니다"라고 말하는 건 해법이 아니다. 그건 도와주는 게 아니라 고문에 가깝다.

템플릿 방식은 맞는 것 같은데, 실무자가 직접 작성하고 수정할 수 있어야 했다.

믿는 구석은 하나 있었다. 실무자들은 엑셀을 정말 잘 쓴다. VLOOKUP, IF, 피벗, 필터, 서식. 개발자가 생각하는 것보다 훨씬 능숙하게 엑셀을 다룬다.

그러면 새 프로그래밍 언어를 가르칠 게 아니라, 이미 잘 쓰는 엑셀 문법의 연장선에 있는 템플릿 언어를 만들면 되지 않을까.

dotxl의 실패

처음 만든 문법은 dotxl이라고 불렀다.

실패했다.

정확히는 엔진이 아예 동작하지 않았다는 의미의 실패는 아니다. 재미있는 실패였는데, LLM에게 템플릿 작성을 시키면 잘 수행하지 못했다.

생각해보면 당연하다. 내가 방금 만든 문법이고, 어디에도 명세가 없고, 학습된 적도 없다. 사람도 헷갈리는 문법을 LLM이 안정적으로 만들 수 있을 리가 없다.

이때 markdown이 부러웠다. LLM이 markdown을 잘 쓰는 이유는 대단한 추론 능력 때문만이 아니다. 표준이 있고, 문서가 널려 있고, 예제가 많다.

명확한 규격과 제한 범위를 정하면 LLM도 템플릿 문법을 만들 수 있을 것 같았다.

그래서 문법을 감으로 늘리는 대신, 내 컨텍스트 일부를 계속 할애해서 표준 규격을 구체화해 나갔다.

목표는 이렇게 잡았다.

머릿속에 있는 엑셀 변환 규칙을 추상화하여 엑셀 파일로 표현 가능하게 만들기.

LLM 시대에는 좋은 스펙 문서가 그 자체로 기능이 될 수 있다. 이 프로젝트에서 꽤 크게 느낀 지점이다.

raw + template = result

그렇게 만들고 있는 게 xl3다.

원본 파일   +   템플릿 파일   =   결과 파일
raw.xlsx   +  template.xlsx  =  result.xlsx

엑셀 파일이 3개라서 xl3.

붕어빵으로 치면 반죽 -> 틀 -> 붕어빵 그대로다.

xl3는 Excel Template Execution Engine이다. ExcelJS나 openpyxl처럼 워크북을 코드로 만드는 라이브러리와는 방향이 다르다.

기존 방식: Workbook을 코드로 만든다.
xl3 방식: Workbook은 이미 존재한다. 데이터만 주입한다.

템플릿 셀에는 이런 식으로 적는다.

{{ [품목명] }}
{{ IF([금액] > 10000, "우선", "일반") }}

[컬럼명]으로 원본 데이터를 참조하고, IF나 VLOOKUP처럼 익숙한 함수 모양을 가져왔다. 새 언어를 배우는 느낌보다, 쓰던 엑셀 문법이 조금 확장되는 느낌이 들도록 만들고 싶었다.

문법 방향도 일부러 좁게 잡았다.

  • 엑셀 함수와 비슷하게 읽혀야 함
  • 실무자가 직접 유지보수할 수 있어야 함
  • LLM이 생성하기 쉬워야 함
  • 범용 프로그래밍 언어가 아니라 문서/보고서 생성을 위한 최소 선언형 언어여야 함
  • 코어 엔진은 .xlsx만 받고, 다른 포맷은 호출하는 쪽에서 어댑터로 처리함

뭐든 할 수 있는 언어를 만들고 싶지는 않았다. 그 순간 ExcelJS 코드를 셀 안에 다시 쓰는 것과 다를 게 없어지기 때문이다.

Exform은 제품 흐름이다

xl3가 엔진이라면 Exform은 실무자가 그 엔진을 쓰기 위한 제품 흐름이다.

엔진만 있으면 개발자는 즐겁지만 실무자는 여전히 막막하다. 그래서 템플릿 등록, 원본 파일 업로드, 변환 실행, 결과 다운로드, 검증까지 이어지는 사내 도구를 만들었다.

흐름은 대략 이렇다.

  1. 지난달에 보낸 결과 파일과 이번 달 원본 파일을 준비한다.
  2. 필요하면 AI Template Builder에 두 파일을 업로드한다.
  3. 에이전트가 변환 패턴을 추론해서 xl3 템플릿 초안을 만든다.
  4. 실무자가 템플릿을 검토하고 필요한 부분을 보완한다.
  5. Exform에 템플릿을 등록한다.
  6. 월말에는 원본 파일만 업로드해서 변환한다.
  7. 결과 파일을 다운로드하고 총액/서식/파일명을 검증한다.

처음에는 "엑셀 변환기" 정도로 생각했는데, 실제로 만들다 보니 필요한 것들이 계속 보였다.

  • 템플릿 등록/교체
  • 템플릿 검색
  • 원본 파일 업로드
  • 결과 파일 다운로드
  • 대량 변환
  • 변환 이력과 로그
  • 결과 검증
  • 런타임 입력값
  • 메뉴 단위 권한 관리
  • 초대 링크와 접근 제한

엑셀 파일 하나 변환하는 일도 제품으로 만들면 주변부가 꽤 많다.

특히 런타임 입력값이 재밌었다. 거래명세서에는 출고일은 있는데 정산 월이 없는 경우가 있다. 그렇다고 템플릿에서 TODAY()를 쓰면 4월 마감을 5월 1일에 작성할 때 5월로 찍힌다.

사람은 "이번 건은 4월 정산이니까 202604로 적어야지"라고 자연스럽게 판단하지만, 엔진은 모른다. 그래서 정산 월 같은 값은 변환 시점에 입력받도록 했다.

이런 디테일이 쌓이면서 Exform은 단순 변환 데모가 아니라 실무자가 월말에 실제로 열어 쓰는 도구가 됐다.

실무 엑셀은 flat하지 않다

엔진이 어느 정도 자리 잡히고, 이를 기반으로 사내 엑셀 변환 도구를 만들었다. 테스트를 위해 TF와 무관한 팀들의 실무 자료도 요청해서 받았다.

여기서 현실의 벽을 만났다.

내가 처음 상상한 raw 엑셀은 단일 table 형태였다.

상품명 | 수량 | 금액 | 출고일

현실의 실무 엑셀은 전혀 그렇지 않았다.

  • 병합된 셀
  • 여러 시트에 걸친 수식
  • 사람이 보기 좋은 비정형 헤더
  • 빈 줄과 설명 줄
  • 중간중간 끼어 있는 합계 행
  • 시트 이름에 숨어 있는 의미

엑셀은 데이터베이스가 아니라 문서다. 사람에게 읽히기 좋게 만들어진 문서.

xl3 엔진을 태우려면 flat화 작업을 작업자가 또 해야 하는 상황이 생겼다. 자동화 도구를 쓰려고 수작업을 해야 하면 본말전도다.

그래서 변환 직전에 실행되는 전처리 단계를 추가했다.

  1. 선택적으로 전처리 스크립트를 통해 원본 데이터를 평탄화한다.
  2. 선택적으로 정산 월 같은 런타임 입력값을 작성한다.
  3. 원본 파일을 첨부한다.
  4. 변환한다.

깔끔한 설계보다 더러운 현실 데이터를 받아들이는 쪽을 택했다.

대신 전처리는 코어 바깥 단계로 뺐다. 모든 현실 예외를 XTL 문법 안으로 끌고 들어오면 엔진 스펙이 비대해진다. xl3는 템플릿 실행 엔진으로 두고, Exform이 실무 흐름을 감싸는 쪽이 낫다고 판단했다.

숫자로 남은 결과

프로젝트가 제일 재미있었던 순간은 "이게 될 것 같다"에서 "이미 쓰고 있다"로 넘어간 순간이었다.

2026년 5월 정산 업무에 실제로 투입됐고, 너무 좋다고 DM이 왔다. 사용하는 팀도 두 곳 더 늘었다. ㅎㅎ

6월 말 기준으로는 더 구체적인 숫자가 남았다.

  • 운영컨설팅 전체 관리 기업 190개
  • 수기 거래명세서 변환 대상 77개 기업
  • 기존 작업 규모는 2인, 영업일 약 5일
  • 목표 절감은 0.5 M/M -> 0.1 M/M 수준
  • 2026-06-25 기준 66개 템플릿 세팅 완료
  • 2026-06-30 기준 77개 중 약 60개 로직을 실무자가 직접 제작 완료

숫자 자체보다 마음에 드는 지점은 "실무자가 직접 제작 완료"라는 표현이다.

개발자가 60개 스크립트를 대신 만들어준 게 아니다. 실무자가 변환 규칙을 템플릿으로 만들고, 등록하고, 실행하는 흐름까지 갔다.

물론 모든 케이스가 순탄했던 건 아니다.

숨김 시트가 active 상태로 저장되면서 엑셀에서 이상하게 동작하는 버그도 있었고, 일부 대형 고객사나 회계법인 고객사처럼 템플릿 난이도가 높은 케이스도 있었다. 어떤 템플릿은 하나로 처리하지 못해서 둘로 나눴고, 검색 기능이나 권한 관리도 운영 중에 필요해졌다.

근데 이런 문제들은 오히려 좋은 신호에 가깝다. 장난감이 아니라 실제 업무에 들어갔기 때문에 나오는 이슈다.

거래명세서 전용 도구는 아니었다

처음 적용한 도메인은 거래명세서였지만, 문제의 모양은 다른 팀에서도 반복됐다.

커머스 쪽에서는 사방넷 주문 취합 정리 업무가 있었다.

쇼핑몰 주문을 모으고, 비정형 채널 주문을 정리하고, 상품명/옵션을 표준 품목명으로 맞추고, 물류센터나 벤더별 출고요청서 양식으로 나누고, 다시 송장번호를 취합한다.

겉으로 보면 다른 업무지만 구조는 비슷하다.

원본 데이터
+ 마스터/매핑 규칙
+ 벤더별 출력 양식
-> 결과 파일

재무팀 개인경비 처리나 사입 정산 자동화도 비슷한 냄새가 났다. 엑셀 기반 수기 검증, 변환, 업로드. 반복 업무인데 예외가 많고, 예외가 많아서 사람 머릿속에 남아 있고, 사람 머릿속에 남아 있으니 시스템화가 어렵다.

결국 Exform은 거래명세서 전용 도구라기보다, 운영 조직의 엑셀 변환 규칙을 파일 기반 자산으로 만드는 도구에 가까워졌다.

변환 규칙은 코드가 아니라 파일에 둔다

정리하면 핵심 결정은 하나다.

변환 규칙을 코드가 아니라 엑셀 파일 안에 둔다.

그러면 그 엑셀 파일은 여러 역할을 한다.

  • xl3 엔진이 읽는 실행 가능한 규칙서
  • 비개발자도 열어볼 수 있는 문서
  • 담당자가 바뀌어도 남는 인수인계서
  • LLM이 생성하고 보완할 수 있는 표준화된 산출물

기존 방식은 이랬다.

실무자 요청
-> 개발자가 변환 규칙 이해
-> 코드 작성
-> 배포
-> 규칙 변경 때마다 반복

Exform/xl3 방식은 이렇게 바뀐다.

실무자가 원본/결과 샘플 준비
-> AI가 템플릿 초안 생성
-> 실무자가 검토/수정
-> Exform 등록
-> 반복 실행

개발자의 일이 사라진 건 아니다. 오히려 더 어려운 부분으로 이동했다.

개별 변환 스크립트를 많이 만드는 대신, 실무자가 자기 로직을 표현할 수 있는 언어와 실행 환경을 만들어야 한다. 문법을 제한해야 하고, 스펙을 문서화해야 하고, 실패했을 때 디버깅할 수 있어야 하고, 권한/로그/검증 같은 제품 흐름도 챙겨야 한다.

그래도 이 방향이 맞다고 느낀다.

AI가 코드 작성 비용을 거의 없애버린 시대에 개발자가 만들어야 할 건 N개의 스크립트가 아니라, 그 N개를 다른 사람들이 만들 수 있게 하는 구조 아닐까.

남은 숙제

아직 할 일은 많다.

복잡한 템플릿은 AI Template Builder의 생성 정확도가 떨어진다. 템플릿이 커질수록 디버깅 경험도 중요해진다. 실무 원본 파일이 flat하지 않은 문제는 전처리 스크립트로 풀고 있지만, 어디까지 표준화할지는 더 봐야 한다.

성능도 숙제다. 5만 행을 넘기면 비용이 커지고, 6만 행 근처에서는 브라우저/Node 스택 한계가 보인다. 병목은 xl3 자체 로직보다 ExcelJS의 XML 직렬화와 GC 압박 쪽에 더 컸다. 그래서 Rust 구현체인 xl3-rs도 따로 보고 있다.

운영 관점에서는 변환 건수, 실패율, 템플릿별 성공률, 검증 결과 같은 지표도 더 잘 남겨야 한다. "좋다"는 피드백도 중요하지만, 반복 업무 자동화는 결국 숫자로 증명되어야 한다.

그래도 매달 말 일주일을 갈아 넣던 분들이 딸깍 한 번으로 끝내는 걸 보면 방향은 맞는 것 같다.

돌아보며

연초의 나는 8시간을 1초로 만들고 꽤 만족했다.

지금 보면 그건 붕어빵을 대신 빚어준 일이었다. 잘 빚었지만 가게 구조는 그대로였다.

진짜 바뀐 건 틀을 만들고 나서다. 정확히는 누구나 틀을 만들 수 있는 규격과 도구를 만들고 나서.

개발자의 일이 "변환 로직을 대신 짜주는 것"에서 "실무자가 자기 로직을 직접 표현할 수 있는 언어와 제품 흐름을 주는 것"으로 바뀌었다.

이 프로젝트가 마음에 드는 이유는 기술적으로 멋진 엔진을 만들었기 때문만은 아니다. 엑셀이라는 아주 현실적인 도구 위에서, 사람 머릿속에 있던 운영 규칙을 실행 가능한 파일로 끄집어냈기 때문이다.

그게 잘 쌓이면 한 사람의 노하우가 아니라 팀의 자산이 된다.