git 저장소가 없어 히스토리·협업 기반이 아예 없던 상태를 연다.
함께 문서를 재편했다. 그동안 문서가 있어도 "이 제품이 뭘 푸는가"와
"어떻게 도는가"를 담은 문서가 없어서, 목표 문장이 backend/frontend
README 두 곳에 복붙돼 있었다 — 상위 문서가 없어 아래로 샌 것이다.
신설
README.md 레포 진입점 + 문서 지도 + 문서 규칙 4가지
AGENTS.md 에이전트·신규 합류자용 함정 목록과 규약
(CLAUDE.md 는 여기로 걸린 심볼릭 링크)
docs/PRODUCT.md 제품 정의 — 문제·사용자·원칙·**non-goals**·성공 기준
docs/ARCHITECTURE.md payload 경계·발행 파이프라인·서빙 결정·앱 분리 설계
이동
backend/docs/DECISIONS.md → docs/DECISIONS.md
백엔드만의 결정이 아니다. 게다가 코드 주석 ~25곳이 이미
`docs/DECISIONS.md` 로 적고 있어 레포 루트 기준으로는 그게 맞다.
갱신
docs/DEPLOY.md 서빙 결정 반영 — nginx 정적 서빙이 지금 경로(3절),
Azure 는 나중에 켤 때(4절)로 분리
docs/ARCHITECTURE.md 사이트 = 한 장(2026-08-31) 구조 반영
docs/COLLECTION_SEO_AEO_FLOW.md
robots.txt·sitemap.xml 은 오리진 루트에만 굽는다는 점 명시
frontend/site/scripts/prerender.ts
헤더 주석의 렌더 보고서 경로가 실제(422줄)와 달라 수정
.gitignore
★ CLAUDE.md 를 더 이상 무시하지 않는다. 에이전트 지침은 팀과 모든
에이전트가 공유하는 규약이라 커밋해야 한다 — 무시하면 클론한 사람이
"배포 후 republish_all.py 필수" 같은 함정을 전달받지 못한다.
개인용 오버라이드는 ~/.claude/CLAUDE.md 에 둔다.
12 KiB
API 사용 이력 · 비용 기록
집계 시각: 2026-08-27 08:59 KST 집계 대상: o2o-web4ai 프로젝트 전체 (backend · frontend/admin · frontend/site)
0. 먼저 — 섞으면 안 되는 두 가지 비용
| 무엇 | 언제 나가나 | 현재 | |
|---|---|---|---|
| A. 제품 원가 | 사이트 1건 만들 때 나가는 외부 API 비용 (Perplexity·Kakao·Gemini) | 업소 1곳 처리할 때마다 반복 | 0원 (아직 한 번도 호출 안 함) |
| B. 개발비 | 이 코드를 짜는 데 쓴 Claude API 비용 | 개발 기간 동안 한 번 | $70.07 |
A 의 상한은 $1(1,400원)이다. B 는 사이트를 만들 때마다 나가는 돈이 아니다 — 코드가 완성되면 끝나는 일회성 지출이다. 이 문서의 2절은 전부 B 얘기다.
0-1. 한 줄 요약
| 구분 | 실제 호출 | 비용 |
|---|---|---|
| 외부 유료 API (Perplexity · Kakao · Gemini · TourAPI) | 0건 | 0원 |
| 개발 도구 API (Claude Code / Anthropic Messages API) | 348 요청 | $70.07 (≈ 98,100원, 1,400원/USD 가정) |
지금까지 나간 돈은 전부 개발 과정의 Claude API 요금이고, 제품이 쓸 외부 API는 아직 한 번도 실제로 때리지 않았다.
1. [A] 제품 원가 — 외부 API 계약 현황과 실호출 이력
1-1. 키 상태
| API | 용도 | 키 설정 | 어댑터 | 실호출 |
|---|---|---|---|---|
Perplexity Sonar (api.perplexity.ai) |
채널 URL 발견 | ✅ 설정됨 | backend/services/external/perplexity.py 구현 완료 |
0건 |
Kakao Local (dapi.kakao.com) |
동일 업소 검증 · 주변 정보 | ⬜ 비어 있음 | 미구현 (설계만) | 0건 |
| Gemini (Google AI Studio) | 사진 분류 · 카피 작성 | ✅ 설정됨 | 미구현 | 0건 |
TourAPI (data.go.kr) |
축제 · 관광지 | ⬜ 비어 있음 | 미구현 | 0건 |
| Open-Meteo | 날씨 | 키 불필요 | frontend/.../useWeather.ts |
0건 (프론트 코드만 작성) |
.env의 키가 비어 있으면 해당 어댑터만 비활성되고 서버는 그대로 뜬다 — 설계상 의도된 동작.
1-2. 실호출이 0건인 근거
- 코드 레벨 — 전체 세션 트랜스크립트에서 위 호스트로 나간 HTTP 요청이 없다. 등장하는 건 전부 소스 작성·문서 편집 텍스트다.
- 테스트 레벨 —
backend/tests/안에서 실제 네트워크를 타는 코드가 없다.test_perplexity.py·test_collector.py는 전부 목(mock) 기반이고,httpx.get/post직접 호출은 0건. - 설정 레벨 —
DECISIONS.md의 결정대로APP_ENV=test면.env를 읽지 않는다. 실키가 테스트로 새어 외부 API 요금이 나가는 경로 자체를 막아 뒀다. - DB 레벨 — 어떤 테이블에도 외부 API 응답이 적재된 흔적이 없다 (호출 로그 테이블도 아직 없다 — 3-2 참고).
1-3. 단가표 (호출 시작 시 적용될 요금)
| API | 과금 방식 | 단가 |
|---|---|---|
| Perplexity Sonar | 토큰 요금 + 검색 호출 요금이 별도로 붙는다 | sonar 기본, sonar-pro 는 더 비쌈. 호출당 검색 횟수는 이미 로그로 남기고 있음 (perplexity.py 의 usage.num_search_queries) |
| Kakao Local | 무료 쿼터 초과분 종량 | 키워드/카테고리 검색 2원, 좌표 변환 0.5원 (키워드가 4배 비싸다) |
| Gemini | 토큰 종량 (Google AI Studio) | 모델·티어별 |
| TourAPI | 공공데이터 무료 (일 트래픽 제한) | 0원 |
| Open-Meteo | 무료 | 0원 |
⚠️ Kakao 무료 쿼터 함정 — 무료 쿼터는 개발자 계정의 "첫 번째 활성 앱" 에만 붙는다. dev/stage/prod 앱을 따로 파면 하나만 무료다. 앱을 나누기 전에 확인할 것.
2. [B] 개발비 — Claude (Anthropic Messages API)
사이트 1건당 원가가 아니다. 코드를 짜는 데 든 일회성 비용이다.
2-1. 총계
| 항목 | 값 |
|---|---|
| 기간 | 2026-08-26 16:23 ~ 2026-08-27 08:58 KST (약 16.6시간) |
| 모델 | claude-opus-5 (1M 컨텍스트) 단일 |
| 과금 요청 수 | 348건 (메시지 ID 기준 중복 제거) |
| 출력 토큰 | 450,509 |
| 캐시 쓰기 (1h TTL) | 2,020,375 |
| 캐시 쓰기 (5m TTL) | 96,213 |
| 캐시 읽기 | 76,004,125 |
| 캐시 미적용 입력 | 690 |
| 입력 환산 합계 | 78,121,403 |
| 총 비용 | $70.07 |
2-2. 비용 내역
| 항목 | 토큰 | 요율 ($/MTok) | 금액 |
|---|---|---|---|
| 캐시 읽기 (기본 입력가 ×0.1) | 76,004,125 | $0.50 | $38.00 |
| 캐시 쓰기 1h (기본 입력가 ×2) | 2,020,375 | $10.00 | $20.20 |
| 출력 | 450,509 | $25.00 | $11.26 |
| 캐시 쓰기 5m (기본 입력가 ×1.25) | 96,213 | $6.25 | $0.60 |
| 캐시 미적용 입력 | 690 | $5.00 | $0.003 |
| 합계 | $70.07 |
각 항목은 센트 단위 반올림이라, 눈으로 더하면 합계와 1센트 차이가 날 수 있다.
캐시 효과 — 프롬프트 캐싱이 없었다면 같은 작업이 $401.87 이었다. 즉 $331.80 (82.6%) 를 캐시로 아꼈다. 동시에, 비용의 54%($38.00)가 캐시 읽기 다. 출력 토큰 값($11.26)의 3배가 넘는다 — 긴 컨텍스트를 반복해서 다시 읽는 패턴이 이 프로젝트 비용 구조를 지배한다는 뜻이다. 줄이려면 출력을 줄이는 게 아니라 컨텍스트를 작게 유지해야 한다.
2-3. 세션별 내역
| 세션 | 종류 | 시각 (KST) | 요청 | 출력 토큰 | 캐시 읽기 | 비용 |
|---|---|---|---|---|---|---|
4db7aca5 |
메인 (backend 주력) | 08-26 16:23 ~ 08-27 08:55 | 157 | 266,385 | 35,748,750 | $32.65 |
99be2a6c |
메인 | 08-26 17:24 ~ 08-27 08:58 | 92 | 124,641 | 16,660,246 | $19.79 |
19be092e |
메인 (frontend/admin) | 08-27 08:42 ~ 08:58 | 29 | 25,903 | 2,476,298 | $3.37 |
agent-a58… |
서브에이전트 | 08-27 08:51 ~ 08:57 | 13 | 6,173 | 5,741,359 | $3.21 |
agent-afa… |
서브에이전트 | 08-27 08:52 ~ 08:55 | 14 | 423 | 6,132,381 | $3.21 |
6052d29a |
메인 (이 기록 작성 세션) | 08-27 08:52 ~ 08:58 | 21 | 23,936 | 1,678,581 | $2.92 |
agent-aa5… |
서브에이전트 | 08-27 08:52 ~ 08:58 | 9 | 68 | 3,905,434 | $2.07 |
agent-ab1… |
서브에이전트 | 08-27 08:52 ~ 08:57 | 8 | 165 | 3,466,831 | $1.90 |
d56c8333 |
메인 | 08-27 08:52 ~ 08:53 | 5 | 2,815 | 194,245 | $0.95 |
2ac02dde |
메인 (중복 세션) | — | 0 | 0 | 0 | $0.00 |
읽을 거리 두 가지:
- 서브에이전트 4개 합계 $10.39 — Perplexity/Kakao 클라이언트를 병렬로 짠 fork 들이다. 출력 토큰은 다 합쳐 6,829개뿐인데 캐시 읽기가 1,925만 토큰이다. fork 는 부모 컨텍스트를 통째로 들고 시작하기 때문에, 실제 작업이 짧아도 부모 컨텍스트가 크면 읽기 요금이 그만큼 붙는다. 병렬 fork 를 쓸지 말지는 "일이 병렬인가" 보다 "부모 컨텍스트가 큰가" 로 판단하는 편이 비용상 맞다.
2ac02dde는99be2a6c로 이어진 세션(fork/resume)이라 메시지가 전부 중복 — 0원으로 처리했다. 세션 간 중복을 제거하지 않으면 408건 / $79.16 이 나와 $9.09 과대집계된다. 재집계할 때 메시지 ID 중복 제거를 반드시 넣을 것.
2-4. 이 숫자를 읽을 때 주의할 점
- 구독제(Max/Pro)로 쓰고 있다면 이 금액이 그대로 청구되지는 않는다. 토큰 사용량을 공개 API 요율로 환산한 "이만큼 썼다" 값이다. 종량제 API 키로 같은 작업을 돌렸다면 나왔을 금액.
- 요율 기준: Opus 5 입력 $5 / 출력 $25 per MTok, 캐시 읽기 ×0.1, 캐시 쓰기 5m ×1.25 · 1h ×2.
- 1M 컨텍스트 구간에 별도 프리미엄이 붙는 경우 실제 금액은 이보다 높을 수 있다 — 하한선으로 읽을 것.
- 원화 환산은 1,400원/USD 가정이다. 실제 청구 환율과 다를 수 있다.
- 이 스냅샷은 찍는 순간에도 움직인다. 집계 시각 기준으로
4db7aca5·99be2a6c·19be092e·6052d29a등 여러 세션이 동시에 살아 있었다. 4절의 스크립트로 언제든 다시 뽑을 것.
3. 앞으로 할 일
3-1. 비용이 실제로 발생하기 시작하는 지점
수집 파이프라인이 도는 순간부터다:
상호명 → [Perplexity: 채널 URL 발견] ← 토큰 + 검색 호출 과금
→ [Kakao Local: 동일 업소 검증] ← 건당 2원
→ 크롤링(무료)
→ [Gemini: 사진 분류 + 카피] ← 토큰 과금
업소 1건당 Perplexity 1~2회 + Kakao 여러 건 + Gemini(사진 수만큼)가 붙는다. 업소당 원가를 먼저 실측한 뒤 배치를 키우는 순서가 안전하다.
3-2. 사이트 1건당 예산: $1 (1,400원) — 코드로 강제한다
제품 제약이 정해졌다: 업소 1곳의 사이트를 만드는 외부 API 비용은 $1 을 넘으면 안 된다.
문서에만 적으면 지켜지지 않으므로 가드를 구현했다 — backend/common/cost.py (CostMeter), 테스트 backend/tests/test_cost.py 8건.
meter = CostMeter(place_id=42)
meter.guard(Provider.PERPLEXITY, searches=2, tokens=3000) # 호출 "전" 에 확인 → 넘으면 BudgetExceeded
...실제 호출...
meter.charge(Provider.PERPLEXITY, searches=2, tokens=3100) # 호출 "후" 에 실측 기록
guard()가 막으면 그 호출을 하지 않는다. 이미 쓴 돈은 못 돌려받지만 손실이 선형으로 늘어나는 걸 끊는다.- 예산 70% 소진 시 경고 로그, 100% 초과 시 예외.
- 미터 1개 = 사이트 1건.
⚠️ 단가표에 아직 추정치가 섞여 있다. 확정된 건 카카오뿐이다(레포에 2원/0.5원이 적혀 있다). Perplexity·Gemini 는 공식 단가표를 확인해서 RATES 를 교체해야 한다. 그때까지 assert_rates_confirmed() 가 실배치를 막는다 — 추정치 위에 "예산 안에 들어온다" 는 결론을 세우지 않기 위해서다.
3-3. 지금 없는 것 — 호출 로그 테이블
현재 DB 스키마(backend/common/database/model/models.py)에 API 호출·비용을 적재하는 테이블이 없다. 지금은 애플리케이션 로그로만 남는다 (perplexity.py 가 tokens= 와 검색 횟수를 찍는 정도).
파이프라인을 돌리기 전에 api_call_logs 같은 테이블(공급자 · 엔드포인트 · 토큰/호출수 · 추정단가 · place_id · 시각)을 얹어두면, 이 문서를 손으로 갱신하지 않고 쿼리로 뽑을 수 있다.
4. 재집계 방법
Claude 사용액은 아래로 다시 뽑는다 (세션 트랜스크립트 기준, 메시지 ID로 중복 제거):
cd ~/.claude/projects && python3 - <<'PY'
import json,glob,collections,os
files=sorted(glob.glob("-Users-minachoi-projects-o2o-web4ai*/*.jsonl")
+ glob.glob("-Users-minachoi-projects-o2o-web4ai*/*/subagents/*.jsonl"),
key=lambda p:-os.path.getsize(p))
IN,OUT,CR,W5,W1H = 5.0/1e6, 25.0/1e6, 0.50/1e6, 6.25/1e6, 10.0/1e6 # Opus 5 요율
seen=set(); t=collections.Counter()
for f in files:
for line in open(f,encoding="utf-8",errors="replace"):
try: o=json.loads(line)
except: continue
m=o.get("message") or {}; u=m.get("usage")
if not u: continue
rid=m.get("id")
if rid:
if rid in seen: continue # ★ fork/resume 중복 제거 — 빠뜨리면 과대집계
seen.add(rid)
cc=u.get("cache_creation") or {}
w5=cc.get("ephemeral_5m_input_tokens",0); w1=cc.get("ephemeral_1h_input_tokens",0)
if w5+w1==0: w5=u.get("cache_creation_input_tokens",0)
t["in"]+=u.get("input_tokens",0); t["out"]+=u.get("output_tokens",0)
t["w5"]+=w5; t["w1h"]+=w1; t["cr"]+=u.get("cache_read_input_tokens",0); t["n"]+=1
cost=t["in"]*IN+t["out"]*OUT+t["w5"]*W5+t["w1h"]*W1H+t["cr"]*CR
print(dict(t)); print(f"요청 {t['n']}건 / ${cost:.2f}")
PY
외부 API 실호출 여부 확인:
cd ~/.claude/projects && grep -c "api.perplexity.ai\|generativelanguage.googleapis.com\|dapi.kakao.com" \
-- -Users-minachoi-projects-o2o-web4ai*/*.jsonl
# 매칭이 있어도 대부분 소스 작성 텍스트다 — tool_use 의 command/url 필드인지 확인할 것