RAG 18. 재무 보고서 검색: 숫자와 기간·통화·단위를 함께 꺼내자
재무 보고서에서 매출을 찾을 때 숫자만 꺼내면 서로 다른 기간이나 통화를 비교하기 쉽습니다. 가상의 연차·분기 보고서를 사용해 비교 조건이 맞는 근거만 고르고, 기준이 다르면 계산을 멈추는 프로그램을 만듭니다.
재무 보고서에서 매출을 찾을 때 숫자만 꺼내면 서로 다른 기간이나 통화를 비교하기 쉽습니다. 가상의 연차·분기 보고서를 사용해 비교 조건이 맞는 근거만 고르고, 기준이 다르면 계산을 멈추는 프로그램을 만듭니다.

그림은 처리 구조를 설명하는 개념도입니다. 실제 실행 화면이나 측정 결과가 아닙니다.
오늘 만들 결과
2024년과 2025년의 가상 연결 매출을 찾아 25.0% 증가를 계산합니다. 같은 2025년이라도 4분기 실적이나 달러 표시 수치는 섞이지 않습니다. 실제 기업 분석이나 투자 판단을 제공하는 글이 아니며, 모든 보고서 이름과 수치는 프로그래밍 실습용입니다.
먼저 이해할 것
RAG에서 검색한 수치를 답변에 쓰려면 그 수치가 뜻하는 범위도 함께 전달해야 합니다. 메타데이터는 본문에 덧붙이는 설명 정보입니다. 여기서는 지표, 시작일·종료일, 연간·분기 구분, 통화, 표시 단위, 연결·별도 범위, 출처 페이지를 메타데이터로 둡니다.
연결(consolidated)은 이 예제에서 여러 회사를 합산한 보고 범위를 가리키는 표식입니다. 별도 실적과 같은 것으로 취급하면 안 됩니다. million은 백만 단위입니다. 따라서 1500.0 KRW million을 1,500원이라고 표시하면 잘못입니다.
이번 편은 PDF 표를 자동 해석하는 프로그램이 아닙니다. 표 추출 뒤 사람이 원문과 대조해 정리했다고 가정한 레코드에서 근거를 찾는 단계입니다. 자연어 문장만으로 기간을 추측하지 않고, 검색 함수에 비교 조건을 명시합니다. 최종 출력도 모델 생성이 아닌 정해진 형식의 근거 요약입니다.
실행 준비와 전체 코드
Python 3.12.14에서 실행했습니다. Python 3.12와 표준 라이브러리만 있으면 됩니다. 빈 실습 폴더를 만들고 아래 파일들을 같은 폴더에 저장하세요. 다른 편의 코드나 유료 API 키는 필요하지 않습니다.
FACTS에 원문을 확인했다고 가정한 가상 재무 레코드가 들어 있습니다. 별도 데이터 파일은 만들지 않습니다.
finance.py
from decimal import Decimal
# Entirely fictional company and reports; values are strings on purpose.
FACTS = [
dict(id='F24-8', report='Fictional Orbit Annual 2024', page=8,
metric='revenue', start='2024-01-01', end='2024-12-31',
period='annual', currency='KRW', unit='million', scope='consolidated', value='1200.0'),
dict(id='F25-8', report='Fictional Orbit Annual 2025', page=8,
metric='revenue', start='2025-01-01', end='2025-12-31',
period='annual', currency='KRW', unit='million', scope='consolidated', value='1500.0'),
dict(id='F25Q4-5', report='Fictional Orbit Q4 2025', page=5,
metric='revenue', start='2025-10-01', end='2025-12-31',
period='quarter', currency='KRW', unit='million', scope='consolidated', value='410.0'),
dict(id='F25USD-8', report='Fictional Orbit USD Supplement', page=8,
metric='revenue', start='2025-01-01', end='2025-12-31',
period='annual', currency='USD', unit='million', scope='consolidated', value='1.1'),
]
def retrieve(**filters):
required = {'metric', 'start', 'end', 'period', 'currency', 'unit', 'scope'}
if set(filters) != required:
raise ValueError('기간, 통화, 단위, 연결/별도 범위를 모두 지정하세요.')
hits = [f for f in FACTS if all(f[k] == v for k, v in filters.items())]
if len(hits) != 1:
raise ValueError('단일 근거를 확정할 수 없습니다. 원문을 확인하세요.')
return hits[0].copy()
def annual(year):
return retrieve(metric='revenue', start=f'{year}-01-01', end=f'{year}-12-31',
period='annual', currency='KRW', unit='million', scope='consolidated')
def growth(previous, current):
keys = ('metric', 'period', 'currency', 'unit', 'scope')
if any(previous[k] != current[k] for k in keys):
raise ValueError('비교 기준이 다릅니다.')
if previous['period'] != 'annual':
raise ValueError('이 실습은 연간 비교만 지원합니다.')
if (previous['start'][5:], previous['end'][5:]) != (current['start'][5:], current['end'][5:]):
raise ValueError('회계기간 경계가 다릅니다.')
if int(current['start'][:4]) != int(previous['start'][:4]) + 1:
raise ValueError('연속된 연도를 지정하세요.')
old, new = Decimal(previous['value']), Decimal(current['value'])
if not old.is_finite() or not new.is_finite() or old <= 0:
raise ValueError('이 예제의 증가율 계산 범위를 벗어났습니다.')
return ((new - old) / old * 100).quantize(Decimal('0.1'))
def describe(f):
return (f"[{f['id']}] {f['start']}~{f['end']} {f['period']} "
f"{f['scope']} revenue={f['value']} {f['currency']} {f['unit']} "
f"({f['report']}, p.{f['page']})")
if __name__ == '__main__':
old, new = annual(2024), annual(2025)
print(describe(old))
print(describe(new))
print(f"가상 매출 증가율: {growth(old, new)}% [{old['id']}, {new['id']}]")
try:
growth(new, FACTS[2])
except ValueError as error:
print('비교 거절:', error)
retrieve는 일곱 조건이 전부 들어왔는지 먼저 검사합니다. 단위나 기간이 빠졌을 때 “가장 그럴듯한 매출”을 고르는 대신 질문을 구체화하도록 멈춥니다. 같은 조건으로 여러 사실이 잡혀도 하나를 임의로 고르지 않습니다. 정정 보고서가 들어온 상황에서는 어떤 버전이 유효한지 먼저 정해야 하기 때문입니다.
growth는 검색과 계산을 분리한 함수입니다. 통화·단위·보고 범위가 같고 연속된 연간 자료인지 확인한 뒤 계산합니다. Decimal에는 문자열을 넘깁니다. 사람이 적은 소수 표기를 계산 출발점으로 유지하려는 선택입니다. 마지막에 소수 첫째 자리로 반올림합니다. 기간 끝의 날짜 문자열 비교는 이 가상 자료처럼 달력 연도를 쓰는 경우만 다룹니다. 회계연도 변경, 52주·53주 회계기간, 회계정책 변경이나 재작성 비교 가능성까지 판정하는 함수는 아닙니다.
출력은 원래 단위를 유지합니다. 원화와 달러를 자동 환산하지 않습니다. 환율의 기준일과 출처 없이 환산하면 검색이 맞아도 비교의 의미가 달라질 수 있습니다.
직접 실행한 결과
python finance.py
아래는 이 글의 가상 데이터로 로컬에서 실제 실행한 출력입니다. 언어 모델이나 임베딩 API를 호출한 결과가 아닙니다.
[F24-8] 2024-01-01~2024-12-31 annual consolidated revenue=1200.0 KRW million (Fictional Orbit Annual 2024, p.8)
[F25-8] 2025-01-01~2025-12-31 annual consolidated revenue=1500.0 KRW million (Fictional Orbit Annual 2025, p.8)
가상 매출 증가율: 25.0% [F24-8, F25-8]
비교 거절: 비교 기준이 다릅니다.
테스트로 경계를 고정하기
다음을 test_example.py로 저장합니다.
import unittest
from finance import FACTS, annual, retrieve, growth
class FinanceTests(unittest.TestCase):
def test_period_currency_and_source(self):
fact = annual(2025)
self.assertEqual(fact['id'], 'F25-8')
self.assertEqual(fact['page'], 8)
self.assertEqual(fact['value'], '1500.0')
def test_growth(self):
self.assertEqual(str(growth(annual(2024), annual(2025))), '25.0')
def test_ambiguous_query(self):
with self.assertRaises(ValueError):
retrieve(metric='revenue')
def test_mismatched_currency_and_period(self):
for other in (FACTS[2], FACTS[3]):
with self.assertRaises(ValueError):
growth(annual(2024), other)
def test_zero_denominator(self):
old = dict(annual(2024), value='0')
with self.assertRaises(ValueError):
growth(old, annual(2025))
if __name__ == '__main__':
unittest.main()
python -m unittest -v
다섯 테스트는 정확한 출처 선택, 증가율, 조건 누락, 통화·기간 혼합, 0으로 나누는 경우를 확인합니다. 실제 공시의 회계적 타당성은 검증하지 않습니다.
test_ambiguous_query (test_example.FinanceTests.test_ambiguous_query) ... ok
test_growth (test_example.FinanceTests.test_growth) ... ok
test_mismatched_currency_and_period (test_example.FinanceTests.test_mismatched_currency_and_period) ... ok
test_period_currency_and_source (test_example.FinanceTests.test_period_currency_and_source) ... ok
test_zero_denominator (test_example.FinanceTests.test_zero_denominator) ... ok
----------------------------------------------------------------------
Ran 5 tests in 0.000s
OK
직접 바꿔 볼 것
annual(2026)을 호출하세요. 자료가 없을 때 추정 수치가 나오는 대신 오류가 발생해야 합니다.F25-8과 조건이 같은 정정 레코드를 하나 더 넣으세요. 중복을 발견한 뒤 원문 버전 선택 규칙을 추가해 보세요.unit을billion으로 바꾼 레코드를 넣으세요. 단위 변환이 필요하다면 배율·원래 값·변환 값을 함께 저장하고 별도 테스트를 작성하세요.- 실제 보고서로 확장할 때에는 표의 제목, 주석, 연결·별도 여부와 정정 여부를 사람이 먼저 검토하세요. 검색 정확도를 투자 수익 예측 능력으로 해석하지 마세요.
참고 자료
- SEC EDGAR API 설명: XBRL 사실을 단위별로 나누며 기간 경계를 주의해야 한다는 공식 데이터 설명입니다. 이 실습은 SEC API를 호출하지 않습니다.
- Python Decimal: 문자열 기반 십진수 계산과 반올림 동작.
RAG 실전 포트폴리오 18/25
이전: 17. 의료 지침 검색의 경계: 가상 교육 프로토콜로 버전·범위 확인하기