📝 한 줄 요약
API 문서도 없고, Word 다운로드도 안 되고, 지식도 파편화돼 있던 파생상품 감사지원 플랫폼이, 9일 만에 금리 자동 조회·조서 다운로드·Obsidian
지식베이스·PDF 성능 최적화까지 갖춘 실무 도구로 바뀌었습니다.
---
바쁘시면 이것만 읽어도 돼요:
- 공식 API 문서가 없는 금융 사이트도, 요청 구조를 분석하면 Python으로 자동 조회가 됩니다.
- Word 다운로드는 백엔드 없이 CDN 한 줄로 됩니다. CSS 변수(var(--color))는 DOCX에서 작동하지 않는다는 점만 주 의하면 됩니다.
- API 중복 호출 방지·한도 초과 오류 구분·전송 크기 제한, 세 가지 방어 코드는 방향을 묶어서 한 번에 요청하면 한 번에 구현됩니다.
- 파일명에 상품_모형_번호 형식을 담으면, 폴더 없이도 내용이 보입니다.
- "고쳐줘" 전에 "왜?" 를 먼저 물으면, AI가 코드를 읽고 원인 두 가지를 동시에 찾아옵니다.
---
🎯 이런 분들께 도움돼요
- 금융·회계 실무에서 외부 데이터를 자동으로 가져오고 싶은 분
- Word/Excel 다운로드 기능을 "나중에 서버 만들면"으로 미루고 있는 분
- 감사·검토 문서들이 파편화돼 있어 일관성 관리가 어려운 실무자
- Obsidian으로 업무 지식베이스를 구축하고 싶은 분
- 브라우저 앱이 느린데 어디서 막히는지 몰랐던 개발자
---
😫 문제 상황 (Before)
파생상품 감사지원 플랫폼을 만들고 있었는데, 세 가지 문제가 쌓여 있었습니다.
첫째, 외부 금리 데이터를 수동으로 조회하고 있었습니다. 감사 시 필요한 무위험이자율·신용스프레드를 민간채권평가사 사이트에서 매번 직접 찾아
입력해야 했습니다. 공식 API는 없었습니다.
둘째, 다운로드 버튼이 alert만 띄우고 있었습니다. 조서를 Word로 내보내야 하는데, 버튼을 눌러도 "준비 중" 메시지만 나왔습니다.
셋째, 감사 지식이 파편화돼 있었습니다. 평가모형 설명, 상품 구조, 실무 가이드라인, 조서 예시가 각자 다른 형태로 흩어져 있었고, 서로 연결되지
않았습니다.
여기에 Gemini API를 호출할 때 중복 요청이 발생하거나 한도 초과 오류가 나도 원인을 알 수 없는 문제까지 있었습니다. 그리고 파일을 업로 드하면 "텍스트
추출 중..." 화면이 너무 오래 떴습니다.
---
🛠️ 사용한 도구
- Claude Code CLI — 전체 작업 실행
- Python requests — KIS 금리 API 조회
- html-docx-js (CDN) — 브라우저에서 DOCX 생성
- SheetJS (CDN) — 브라우저에서 Excel 생성
- Obsidian — 지식베이스 뷰어
- PDF.js — 브라우저에서 PDF 텍스트 추출
---
🔧 작업 과정
---
Phase 1 — 금리 자동 조회: 공식 API 없이 만든 fetcher (6/2)
KIS채권평가 사이트(bond.co.kr)에는 공식 API 문서가 없습니다. 그런데 브라우저 개발자 도구로 네트워크 요청을 보면 엔드포인트 구조가 그대로
드러납니다.
▎ "bond.co.kr에서 금리를 가져오는 Python 모듈을 만들어줘. 날짜·신용등급·평가사를 파라미터로 받고, 서버에서 중계해서 CORS 문제도 해결해줘."
요청 구조를 분석하니 단일 엔드포인트에 JSON body를 POST하는 방식이었습니다. 여기서 주말·공휴일 처리가 필요했습니다. 금융 데이터는 영업일
기준으로만 존재하기 때문에, 입력 날짜가 주말이면 직전 영업일 데이터를 자동으로 반환하도록 처리했습니다.
# kofiabond_fetcher.py
KIS_URL = "https://www.bond.co.kr/kisnet/500"
def fetch_rates(date: str, rating: str, agencies: list[str]) -> dict:
# 주말이면 직전 영업일로 자동 조정
d = toprev_business_day(datetime.strptime(date, "%Y-%m-%d"))
# POST body를 그대로 재현
payload = buildpayload(d, rating, agencies)
resp = requests.post(KIS_URL, json=payload, headers=KIS_HEADERS)
return _parse(resp.json())
server.py가 브라우저와 이 fetcher 사이를 중계해서 CORS 문제를 해결했습니다. 평가 화면에서 날짜와 신용등급을 선택하고 버튼 하나를 누르면
무위험이자율과 신용스프레드가 자동으로 채워집니다.
---
Phase 2 — Word·Excel 다운로드: CDN 두 줄로 해결 (6/2)
다운로드 버튼이 alert만 띄우는 상태였습니다. 백엔드를 새로 만들지 않고 브라우저에서 직접 파일을 생성하는 방법이 필요했습니다.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/html-docx.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/xlsx.full.min.js"></script>
한 가지 함정이 있었습니다. CSS 변수(var(--teal))는 DOCX 안에서 동작하지 않습니다. DOM에서 innerHTML을 그대로 뽑으면 색상이 모두 날아갑니다. 데이터
변수로 HTML을 직접 빌드하면서 명시적인 색상값을 넣었습니다.
function downloadWord() {
const html = `<!DOCTYPE html><html><head><style>
th { background-color: #e8edf5; } /* var(--) 대신 직접 값 */
td { border: 1px solid #d0d7e3; padding: 4px 8px; }
</style></head><body>
${buildDocumentHtml(State.getAll())}
</body></html>`;
const blob = htmlDocx.asBlob(html);
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 파생상품감사조서_${info.issuer}_${baseDate}.docx;
a.click();
}
---
Phase 3 — API 방어 코드: 방향 3개를 묶어서 한 번에 (6/2)
Gemini API 호출 문제를 분석하고 해결 방향 세 가지를 직접 정리해서 한 번에 요청했습니다.
▎ "방법 1: PDF 텍스트 전송 크기를 4,000자로 제한. 방법 2: 버튼 중복 클릭 방지 플래그 추가. 방법 3: 429 에러와 일반 에러를 구분해서 안내. 세 가지
모두 step2.html에 반영해줘."
하나씩 순서대로 요청하는 대신 방향을 묶어서 한 번에 줬습니다. 관련 코드가 한 번에 반영됐습니다.
// 방법 1: 전송 크기 제한
const prompt = ...${pdfText.slice(0, 4000)};
// 방법 2: 중복 호출 방지
let isSending = false;
async function analyze() {
if (isSending) return;
isSending = true;
try { /* API 호출 */ }
finally { isSending = false; }
}
// 방법 3: 429 구분 안내
const is429 = e.status === 429 || e.message?.includes('RESOURCE_EXHAUSTED');
const banner = is429
? '❌ API 사용량 한도 초과 — 잠시 후 다시 시도해 주세요.'
: ❌ 오류: ${e.message};
---
Phase 4 — Obsidian 지식베이스 구축: 파편화된 지식을 연결하기 (6/2~6/6)
상품 설명·평가모형·가이드라인·조서 예시가 각각 다른 형식으로 흩어져 있었습니다. 이것들을 Obsidian 위키링크로 연결된 지식베이스로 정리했습니다.
파일명에 분류 기준을 담았습니다.
Before: 조서예시_CB_TF_1.md (참고자료 폴더 안, 단독 파일)
After:
02_상품/CB_전환사채.md ← 상품 구조
03_평가모형/TF_Tree모형.md ← 평가 방법론
03_평가모형/BDT_모형.md
06_조서예시/CB_TF_1.md ← 실제 조서 예시
각 파일에 YAML frontmatter로 메타데이터를 추가하고, 이미지는 Obsidian 위키링크 형식으로 변환했습니다.
---
tags: [CB, TF모형, 조서예시, 전환사채]
product: "[[CB_전환사채]]"
model: "[[TF_Tree모형]], [[BDT_모형]]"
오차허용범위: "±5%"
관련가이드라인: "[[실무_가이드라인]]"
---
# CB_1 조서 예시
...
![[TF모형_tree구조.png]] ← Obsidian 위키링크
![[BDT_이자율상승하락비율_산출방법.png]]
최종적으로 만들어진 파일 구조입니다.
┌───────────────┬─────────┬──────────────────────────────────────────┐
│ 폴더 │ 파일 수 │ 내용 │
├────────── ─────┼─────────┼──────────────────────────────────────────┤
│ 02_상품 │ 5개 │ CB, RCPS, EW, SO, SA │
├───────────────┼─────────┼──────────────────────────────────────────┤
│ 03_평가모형 │ 7개 │ BDT, CRR, GS, HW, LSMC, TF, 부트스트래핑 │
├───────────────┼─────────┼──────────────────────────────────────────┤
│ 04_가이드라인 │ 1개 │ 실무 체크리스트 │
├───────────────┼─────────┼──────────────────────────────────────────┤
│ 05_워크플로우 │ 4개 │ 감사인·고객사 × 평가·질의 │
├───────────────┼─────────┼──────────────────────────────────────────┤
│ 06_조서예시 │ 6개 │ CB(3), RCPS(2), SO(1) │
└───────────────┴─────────┴──────────────────────────────────────────┘
파일을 열기 전에 파일명만 봐도 어떤 상품, 어떤 모형, 몇 번째 예시인지 알 수 있습니다.
---
Phase 5 — PDF 성능 최적화: "왜 느려?"를 먼저 물었다 (6/9)
파일을 업로드하면 "텍스 트 추출 중..." 이 너무 오래 떴습니다. "고쳐줘" 전에 "왜 느린지 봐줘"라고 먼저 물었습니다.
코드를 읽고 원인 두 가지를 동시에 찾아냈습니다.
원인 1 (주범): PDF.js 워커 파일(1.1MB)을 파일 업로드 시점마다 CDN에서 다운로드하고 있었습니다.
// 업로드할 때마다 unpkg.com에서 1.1MB 다운로드
pdfjsLib.GlobalWorkerOptions.workerSrc =
'https://unpkg.com/[email protected]/legacy/build/pdf.worker.min.js';
원인 2: 페이지를 1장씩 순서대로 처리하는 for await 루프.
for (let i = 1; i <= maxPages; i++) {
const page = await pdf.getPage(i); // 한 장 끝나야 다음 장 시작
...
}
두 원인을 파악하고 한 번에 수정을 요청했습니다.
# 1. 워커 파일 로컬 저장 (한 번만)
curl -L -o "공통/js/pdf.worker.min.js" \
https://unpkg.com/[email protected]/legacy/build/pdf.worker.min.js
// 2. CDN → 로컬
pdfjsLib.GlobalWorkerOptions.workerSrc = '../../공통/js/pdf.worker.min.js';
// 3. 순차 → 병렬
const pageTexts = await Promise.all(
Array.from({ length: maxPages }, (_, i) =>
pdf.getPage(i + 1).then(p => p.getTextContent())
.then(tc => tc.items.map(item => item.str).join(' '))
)
);
---
✅ 결과 (After)
┌─────────────────┬─────────────────────────┬──────────────────────────────┐
│ 항목 │ Before │ After │
├─────────────────┼─────────────────────────┼──────────────────────────────┤
│ 금리 데이터 │ 수동 조회·입력 │ 날짜+등급 선택 후 버튼 1번 │
├─────────────────┼─────────────────────────┼──────────────────────────────┤
│ 다운로드 기능 │ alert만 표시 │ Word·Excel 실제 파일 생성 │
├─────────────────┼─────────────────────────┼──────────────────────────────┤
│ API 오류 안내 │ 원인 불명 │ 429·일반 오류 구분 안내 │
├─────────────────┼─────────────────────────┼──────────────────────────────┤
│ 감사 지식 │ 파편화, 형식 불일치 │ 23개 파일 위키링크로 연결 │
├─────────────────┼─────────────────────────┼──────────────────────────────┤
│ 파일명 │ 내용 알려면 열어봐야 함 │ 상품·모형·번호 형식으로 분류 │
├─────────────────┼─────────────────────────┼──────────────────────────────┤
│ PDF 텍스트 추출 │ CDN 대기 + 순차 처리 │ 로컬 로드 + 병렬 처리 │
└─────────────────┴─────────────────────────┴──────────────────────────────┘
---
💬 이 과정에서 배운 AI 활용 팁
1. "고쳐줘" 전에 "왜?"를 먼저 물어라
"느린데 고쳐줘"가 아니라 "왜 느린지 코드를 읽고 찾아줘"라고 했습니다. AI가 파일을 직접 읽고 원인 두 가지를 동시에 짚었습니다. 원인이 명확해지면
수정은 자연스럽게 따라옵니다.
2. 방향을 여러 개 직접 정리해서 한 번에 줘라
API 방어 코드 세 가지를 각각 따로 요청하지 않고, 방향을 묶어서 한 번에 줬습니다. 관련 파일에 동시에 반영됐습니다. 분석은 사람이 하고, 구현은 AI가
한다는 역할 분리가 핵심입니다.
3. 파일명이 분류 체계다
CB_TF_1.md는 폴더 구조 없이도 전환사채·TF모형·첫 번째 예시라는 정보를 담고 있습니다. 파일명 규칙을 먼저 정의하고 나면, 이후 추가되는 파일들도
자연스럽게 같은 규칙을 따릅니다.
4. CDN 라이브러리는 한 번만 받아두면 된다
외부 CDN에 의존하는 라이브러리가 있으면 curl 한 줄로 로컬에 저장하고 경로만 바꾸면 됩니다. 인터넷 속도, CDN 장애, 오프라인 환경 걱정이 한 번에
사라집니다.
---
이렇게 하면 안 돼요
1. "API 문서 없으면 못 한다"고 포기하기 — 브라우저 네트워크 탭에 요청 구조가 다 보입니다.
2. Word 다운로드를 "서버 만들면 할게"로 미루기 — CDN 한 줄이 즉시 해결합니다.
3. 방어 코드를 하나씩 따로 요청하기 — 방향을 묶어서 한 번에 주면 훨씬 빠릅니다.
4. 느린 기능을 "원래 그런가"로 넘기기 — 원인을 물으면 코드에서 바로 찾아냅니다.
5. for await 루프가 느린 이유를 모른 채 쓰기 — 항목이 독립적이면 Promise.all로 바꾸면 됩니다.
---
🌍 다른 업무에 적용한다면?
"API 없는 사이트 자동 조회" 패턴:
[사이트]에서 [데이터]를 가져오고 싶어.
브라우저 개발자 도구 Network 탭 기준으로
실제 요청 구조를 분석해서 Python requests로 재현해줘.
주말·공휴일이 있으면 직전 영업일로 자동 처리도 넣어줘.
"방향 묶어서 한 번에" 패턴:
[문제]에 대한 해결 방향이 [N]가지야:
방법 1: [구체적 조치]
방법 2: [구체적 조치]
방법 3: [구체적 조치]
[대상 파일]에 모두 반영해줘.
"성능 원인 진단" 패턴:
[파일명]을 읽고 [기능]이 [증상]인 이유를 찾아줘.
고치기 전에 원인이 어디에 있는지, 몇 번 줄인지 먼저 알려줘.
---
📋 재사용 가능한 프롬프트
프롬프트 1: CDN 라이브러리 로컬화
[파일명]에서 CDN으로 불러오는 외부 스크립트를 찾아줘.
각각 curl로 로컬에 저장하고 경로를 [로컬 경로]로 바꿔줘.
프롬프트 2: Obsidian 지식베이스 구조화
[파일들]을 Obsidian 형식으로 변환해줘.
- YAML frontmatter에 tags, 관련 문서 링크 추가
- 이미지 경로를 ![[파일명]] 형식으로 변환
- 파일명은 [상품]_[모형]_[번호].md 형식으로
프롬프트 3: 방어 코드 일괄 적용
[파일]의 API 호출 부분에 아래 세 가지를 반영해줘:
1. 중복 호출 방지 플래그 (isSending)
2. 전송 데이터를 [N]자로 제한
3. [상태코드] 에러와 일반 에러를 구분해서 다른 메시지 표시
---
9일 동안의 핵심은 기능 하나씩 완성하는 것이 아니라, 작업마다 원인부터 파악하고 방향을 묶어서 구현한다는 흐름이었습니다. "왜?" 한 마디와 "세 가지
모두 반영해줘" 한 문장이 반복됐고, 그 패턴이 쌓여서 플랫폼이 됐습니다.