AI 견적서 만들기 — 고객 문의 메일 + 원가표로 견적 초안 자동 생성
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
견적 작성, 손이 가는 부분은 늘 정해져 있다
견적 업무는 패턴이 늘 같습니다. 고객이 메일로 "이러이러한 품목들 견적 좀 주세요"라고 보냅니다. 그러면 담당자가 그 메일을 읽고, 품목을 하나씩 원가표에서 찾아, 단가를 옮겨 적고, 수량을 곱해 견적서를 만듭니다. 품목이 열 개면 열 번 표를 뒤집니다.
이 과정의 90%는 기계가 할 수 있는 일입니다. 담당자가 실제로 하는 일을 풀어 쓰면 이렇습니다.
- 고객 메일(자연어)에서 품목·규격·수량을 뽑아낸다
- 그 품목을 내 원가표와 맞춰 단가를 찾는다
- 단가 × 수량으로 1차 견적 초안을 만든다
사람은 마지막에 초안을 검토·조정만 하면 됩니다. 이 글에서는 이 셋을 LLM과 코드로 묶어 "고객 메일을 던지면 견적 초안이 나오는" 자동화를 만듭니다. ChatGPT·Gemini 같은 LLM이면 무엇으로든 됩니다(여기서는 Gemini로 구현).

전체 그림
구성은 단순합니다. 크게 세 덩어리로 나뉩니다.
- LLM 파서 — 비정형 메일을 구조화된 품목 목록(JSON)으로
- 원가표 — 품목별 단가 데이터 (스프레드시트/CSV)
- 매칭기 — 추출된 품목을 원가표와 맞추는 로직
이 솔루션을 굴러가게 하는 건 "원가표를 어딘가 올려두고, 고객 메일 원문을 그대로 시스템에 던지는" 단순한 구조 하나입니다. 그러면 시스템이 원가표를 참조해 1차 견적 초안을 만듭니다.
실제로 이 패턴을 앱으로 구현하면 이런 화면이 됩니다. 고객 정보·견적 항목·설정을 한 화면에서 다루고, 고객 메일을 붙여넣으면 항목이 자동으로 채워집니다.

1단계 — 고객 메일을 품목 JSON으로 (Gemini)
고객 메일은 제각각입니다. "U볼트 M10 50개", "유볼트 m10 50ea", "U-BOLT(M10) 수량 50"이 모두 같은 뜻입니다. 이 비정형 텍스트를 구조화하는 게 LLM이 가장 잘하는 일입니다.
import google.generativeai as genai
import json
genai.configure(api_key=GEMINI_API_KEY)
model = genai.GenerativeModel("gemini-2.0-flash")
PROMPT = """너는 견적 요청 메일에서 품목을 추출하는 파서다.
아래 메일에서 품목명(name)·규격(spec)·수량(qty)을 뽑아 JSON 배열로만 답하라.
규격이 없으면 spec은 빈 문자열로 둔다.
메일:
{email}"""
def parse_items(email_text: str) -> list:
resp = model.generate_content(
PROMPT.format(email=email_text),
generation_config=genai.types.GenerationConfig(
temperature=0.1, # 환각 억제 — 있는 것만 뽑게
response_mime_type="application/json", # JSON 형식 강제
),
)
return json.loads(resp.text)
여기서 두 가지가 중요합니다.
temperature=0.1— 낮게 둡니다. 견적 파싱에서 필요한 건 정확성뿐입니다. 온도가 높으면 메일에 없는 품목을 지어냅니다.response_mime_type="application/json"— Gemini가 JSON만 반환하도록 강제합니다. 이게 없으면 "네, 추출하겠습니다…" 같은 설명이 섞여json.loads가 깨집니다.
입력과 출력은 이렇게 됩니다.
입력(메일): "안녕하세요, U볼트 M10 50개랑 육각볼트 M8*30 200ea 견적 부탁드립니다."
출력(JSON):
[
{"name": "U볼트", "spec": "M10", "qty": 50},
{"name": "육각볼트", "spec": "M8*30", "qty": 200}
]

2단계 — 원가표 준비
원가표는 거창할 필요 없습니다. 스프레드시트(또는 CSV) 한 장이면 됩니다. 최소 컬럼은 품목명·규격·단가입니다.
name, spec, unit_price
U볼트, M10, 1200
U볼트, M12, 1500
육각볼트, M8*30, 90
육각볼트, M10*40, 130
import pandas as pd
cost = pd.read_csv("cost_table.csv") # name, spec, unit_price
3단계 — 품목 매칭 (이게 진짜 어려운 부분)
여기가 핵심이자 함정입니다. 고객이 쓴 품목명과 내 원가표의 품목명이 글자 그대로 일치하는 경우는 거의 없습니다. "U볼트" vs "U-BOLT", "육각볼트" vs "육각 볼트(HEX)". 그래서 정확히 일치(=)로 찾으면 대부분 못 찾습니다.
해결책은 유사도 기반 fuzzy 매칭입니다. 이름과 규격의 유사도를 따로 재서 가중 합산합니다.
from difflib import SequenceMatcher
def sim(a: str, b: str) -> float:
return SequenceMatcher(None, a, b).ratio()
def match(item: dict, cost) -> tuple:
best, best_score = None, 0.0
for _, row in cost.iterrows():
name_score = sim(item["name"], row["name"])
spec_score = sim(item.get("spec", ""), row["spec"])
# 이름 비중을 더 크게(0.6), 규격은 0.4
score = name_score * 0.6 + spec_score * 0.4
if score > best_score:
best, best_score = row, score
return best, best_score
가중치는 경험에서 나왔습니다. 이름 0.6 / 규격 0.4 — 이름이 더 결정적이라 비중을 높이되, 규격(M10 vs M12)이 단가를 가르므로 규격도 무시 못 합니다. 규격이 정확히 일치하면 보너스 점수를 더 주는 식으로 다듬을 수도 있습니다.
4단계 — 견적 초안 생성
매칭이 끝나면 단가 × 수량으로 줄별 금액을 만듭니다. 핵심은 확신이 낮은 항목은 자동 확정하지 말고 사람에게 넘기는 것입니다.
def build_quote(items: list, cost) -> list:
quote = []
for item in items:
row, score = match(item, cost)
flagged = score < 0.5 # 애매하면 사람 검수
unit_price = int(row["unit_price"]) if row is not None else 0
quote.append({
"name": item["name"],
"spec": item.get("spec", ""),
"qty": item["qty"],
"unit_price": unit_price,
"amount": unit_price * item["qty"],
"matched": None if row is None else row["name"],
"needs_review": flagged, # 사람이 확인할 항목 표시
})
return quote
매칭 점수가 0.5 미만이면 needs_review로 표시해서, 사람이 그 줄만 확인하게 합니다. AI가 만든 건 어디까지나 "초안"이고, 최종 책임은 사람이라는 선을 코드로 그어 두는 겁니다.

실제로 만들며 부딪힌 것들
- 품목명 표기 흔들림 — 같은 제품을 고객마다 다르게 씁니다(U볼트/유볼트/U-BOLT). 정확 일치로는 절대 못 찾습니다. fuzzy 매칭이 필수인 이유입니다.
- 규격 표기 혼재 — 인치(1/2", 3/8")와 미터(M8, M10)가 섞입니다. 규격 정규화 전처리를 한 번 거치면 매칭률이 올라갑니다.
- LLM 환각 — 온도를 안 낮추면 메일에 없는 품목을 친절히 "추가"합니다.
temperature=0.1+ JSON 강제가 안전장치입니다. - 자동 확정의 유혹 — 매칭이 그럴듯해 보여도 단가는 돈입니다. 점수 낮은 항목은 반드시 사람이 보게 막아야 합니다.
견적을 넘어서 — 같은 패턴의 다른 쓸모
이 구조를 한 줄로 추상화하면 이렇습니다.
내 데이터(원가표) + 고객의 자연어 요청(메일) → LLM이 1차 초안
견적은 그 첫 사례일 뿐입니다. 같은 패턴으로:
- 고객 문의 메일 → 제품 스펙표 참조 → 답변 초안
- 자재 요청 → 재고표 참조 → 발주서 초안
- 민원 → 규정집 참조 → 답변 초안
"비정형 요청을 받아 내 데이터에 비춰 정형 초안을 만든다"는 일은 어디에나 있습니다. 구조화는 LLM에게 JSON 강제로 맡기면 되고, 이름과 규격이 제각각 흔들리는 건 fuzzy 매칭이 흡수해 줍니다. 그리고 확신이 낮은 항목은 자동으로 확정하지 말고 사람 손에 남겨 두면 됩니다. 이 세 가지만 지키면 도메인이 견적이든 답변이든 발주든 같은 뼈대로 굴러갑니다.
자주 묻는 질문
Q. ChatGPT로도 되나요?
A. 됩니다. 여기서는 Gemini를 썼지만, JSON 출력을 강제할 수 있는 LLM이면 무엇이든 같은 구조로 동작합니다. 핵심은 모델이 아니라 "구조화 + 매칭 + 검수" 패턴입니다.
Q. 원가표가 수백 개면 매칭이 느리지 않나요?
A. 항목이 많으면 전수 비교 대신 임베딩 기반 검색(벡터 유사도)으로 바꾸면 빨라집니다. 다만 수백 개 수준이면 fuzzy 전수 비교로도 충분히 빠릅니다.
Q. AI가 만든 견적을 그대로 보내도 되나요?
A. 안 됩니다. 단가는 금액이라 오차가 곧 손실입니다. AI는 1차 초안까지, 확정과 발송은 사람이 검토한 뒤에 하는 것을 권합니다.
이 글의 코드·표·수치는 모두 설명용 예시입니다. 실제 원가·거래처·고객 정보는 포함하지 않습니다. (2026년 6월 기준)
같은 주제 다른 글 — Claude Code로 직접 만들기
- 클로드 코드 비용 줄이는 법 — ollama 로컬 LLM 연동 (M3 맥북)
- Claude Code로 만족도 설문 만들기 — 교육자료만 주고 30분 만에 맞춤 설문
- 캔바로 2주 걸릴 강의 슬라이드 60장, 클로드 코드로 하루 만에 만들었다
- Claude Code로 회사 홈페이지 만들기 — Next.js + 카페24 쇼핑몰 연동까지
- 클로드 스킬 만드는 법: 3C 분석 프레임워크를 재사용 가능한 Skill로 설계했습니다
- 유튜브 플레이리스트 웹사이트 만들기 — 손글씨 146곡 선물 (GitHub Pages)
- 클로드 코드로 인스타그램 카드뉴스(캐러셀) 자동화 시스템 만들기
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기