모든 자동화 개발자는 꿈을 꿉니다. 어떤 형태의 문서든, 마법처럼 데이터를 빨아들여 깔끔하게 정리하는 완벽한 파이프라인을 말이죠. 저 역시 PDF 자동화라는 멋진 꿈을 꿨습니다. 하지만 몇 주간의 실험 끝에, 저는 그 꿈에서 깨어나기로 결정했습니다. 이 글은 그 결정의 이유와, 꿈보다 더 값진 '현실'을 얻게 된 기록입니다.
🚩 PDF 파싱의 '멋짐'과 '현실'
PDF 파싱은 겉보기엔 멋집니다. 몇 줄의 코드로 문서에서 표를 뽑아내고, 텍스트를 긁어오면 끝날 것 같죠. 하지만 실무에서 마주친 벽은 다음과 같았습니다.

- 서식 지옥: 표가 표가 아니다
- 줄 간격, 병합 셀, 숨은 경계선, 회전 텍스트… 형식이 제각각이라 테이블 인식률이 불안정합니다.
- 한 업체는
W1500×H1200, 다른 업체는1500x1200, 또 다른 곳은1500*1200(mm)로 표기합니다.
- 유지보수 비용 폭증
- 공급사 서식이 바뀔 때마다 파서(rule)를 수정해야 합니다.
- “이번 달 서식”을 맞춰도, 다음 달에 다시 깨집니다.
- 검증 어려움
- 추출 결과가 맞는지 자동 검증 포인트를 잡기 어렵습니다.
- 숫자 한 칸 밀려 들어가도 육안 확인 없이는 놓치기 쉽습니다.
요약: PDF는 사람이 보기엔 ‘문서’지만, 기계에게는 ‘그림’에 가깝습니다.
✅ Excel 기반으로 전환한 이유 (실무형 체크리스트)
다음 조건을 충족시키기 위해 저는 Excel(또는 CSV, 스프레드시트) 기반으로 전환했습니다.
- 스키마 고정: 컬럼명(예:
material_name, standard, unit, qty, unit_price)을 합의하고 그대로 받기 - 단위 정규화:
㎡, m², M2→M2로 자동 매핑, 파이프는 m로 통일 - 파싱 대신 로딩: 구조가 명확한 테이블은 읽기(load) 만 하면 끝
- 검증 가능성: 누락/이상치 체크를 룰로 만들기 (예: 단가 음수 금지, qty=0 금지)
물론 '협력사에게 서식을 바꿔달라고 요청하는 건 현실적으로 어렵다'는 반론이 나올 수 있습니다.
이것이 바로 저의 '역제안'이 들어간 지점입니다. 저는 그들에게 변화를 '요구'하는 대신,
그들의 일을 '도와주는' 엑셀 템플릿을 먼저 제공했습니다.
'여기에 복사/붙여넣기만 하시면 됩니다'라는 제안을 거절할 협력사는 없었습니다.
🔁 전환 후 파이프라인 (간단/견고/예측 가능)
flowchart LR
A[템플릿 Excel 수신] --> B[데이터 로딩 (pandas.read_excel)]
B --> C[정규화 (단위/문자열/폭 파싱)]
C --> D[유효성 검증 (누락/이상치)]
D --> E[보고서 생성 (견적/발주/자재내역서)]
E --> F[WIP 시스템 연동 (진행률/통계)]
- 로딩:
pd.read_excel()한 번이면 끝납니다. - 정규화: 예) 폭 파싱
parse_width_m_from_standard("W1500×H1200") -> 1.5 - 검증: 룰 위반 시 즉시 메시지 표시 → 현장에서 바로 수정
- 생성: Openpyxl로 템플릿 셀에 값 채워넣기 → 서식 유지/다운로드 버튼 제공
🧩 핵심 코드 스니펫 (발췌)
1) 폭(m) 파싱 & 단위 정규화
import re
UNIT_MAP = {"㎡":"M2", "m²":"M2", "M²":"M2", "m2":"M2"}
def normalize_unit(u: str) -> str:
return UNIT_MAP.get(str(u).strip(), str(u).strip().upper())
def parse_width_m_from_standard(std: str, default_m=2.0) -> float:
if not std:
return default_m
s = str(std).upper()
# 숫자 3~4자리 (mm) 우선 추출
m = re.search(r"(\d{3,4})\s*(?:MM)?", s)
if m:
mm = float(m.group(1))
return round(mm / 1000.0, 3)
return default_m
2) 파이프 환산 비고 (“6m × n본”)
def pipe_notes(length_m: float, stock_len_m: float = 6.0) -> str:
if length_m <= 0:
return ""
n = max(1, round(length_m / stock_len_m))
return f"{int(stock_len_m)}m × {n}본"
3) 템플릿 쓰기 (Openpyxl)
from openpyxl import load_workbook
wb = load_workbook("templates/발주서템플릿.xlsx")
ws = wb.active
ws["F3"] = round(bom_df["길이(m)"].sum(), 3) # 모델폭 총합(m)
for i, row in enumerate(result.itertuples(), start=10):
ws[f"A{i}"] = row.material_name
ws[f"B{i}"] = row.standard
ws[f"C{i}"] = normalize_unit(row.unit)
ws[f"D{i}"] = row.quantity
ws[f"E{i}"] = row.unit_price
ws[f"F{i}"] = pipe_notes(row.length_m)
wb.save("output/발주서_자동생성.xlsx")
📊 전환 효과 (숫자로 보는 성과)
- 구현 속도: PDF 파서 튜닝에 주 2
3회 소요 → **Excel 템플릿 합의 후 유지보수 010분/주** - 오류율: 파싱 오류로 인한 필드 누락/오인식 사라짐 → 입력 검증 중심으로 변경
- 확장성: 새로운 업체 추가 시 “템플릿 공유”로 끝 → 파서 추가 구현 불필요
- 체감 효율: 견적/발주/자재내역서 생성 4시간 → 10분 (개발기 #1 참조)
💡 Aegis_BIMer의 실무형 기술 선택 4원칙
- 데이터는 '파싱(Parsing)'하는가, '로딩(Loading)'하는가?
- 서식은 '대응(Reactive)'하는가, '주도(Proactive)'하는가?
- 검증은 '수동(Manual)'인가, '자동(Automated)'인가?
- 협력사는 '불편'해지는가, '편리'해지는가?
결론: 가장 멋진 기술이 아니라, 가장 문제를 잘 푸는 기술이 최고의 기술이다.”
마무리
PDF 파싱을 버린 건 “포기”가 아니라 “선택”이었습니다.
Project Aegis가 해결해야 하는 문제는 정확하고 빠른 견적/발주/자재 관리입니다. 그 목표를 가장 단단하게 달성할 수 있는 길이 Excel 기반이었고, 실제로 속도/정확성/유지보수성 모두에서 성과를 얻었습니다.
다음 글에서는 이 Excel 파이프라인을 WIP(공정 관리) 시스템과 어떻게 연결했는지, 데이터가 견적 → 발주 → 진행률로 흐르는 구조를 사례와 함께 공유드리겠습니다.
'Project_Aegis > [BIM & Automation]' 카테고리의 다른 글
| 앱 성장기, 단일 고객용 툴에서 멀티테넌트 SaaS로의 진화 (1) | 2025.10.14 |
|---|---|
| [개발기 #4] WIP 공정관리 시스템 v0.7 — Supabase로 완전 전환한 실시간 제조 관리 앱의 탄생기 (1) | 2025.10.12 |
| [개발기 #1] 매일 4시간 걸리던 엑셀 노가다, Python 코드 2줄로 10분 만에 끝낸 방법 (0) | 2025.10.06 |
| 파이썬 3주만에 '금속 구조물 자동화 시스템' 프로토타입 v1.0 개발기 (0) | 2025.09.22 |
| [Project Aegis] 파이프 자동 단가 시스템 v0.1 개발기: PDF 파싱의 현실적인 한계와 새로운 가능성 (0) | 2025.09.20 |