수집한 재무 데이터를 어떻게 자동으로 검증하나
Quick Summary

수천 종목의 재무제표 데이터를 수집한 뒤, 매핑 오류·누락·이상치를 자동으로 잡아내는 검증 파이프라인을 설계한다. BS 항등식, 시계열 연속성, 업종별 기대값 검증까지 실전 전략을 다룬다.

수집한 재무 데이터를 어떻게 자동으로 검증하나

재무 데이터를 수집하는 건 반쪽이다. 나머지 반은 수집한 데이터가 진짜 맞는지 확인하는 것이다.

XBRL을 파싱하고, 계정을 매핑하고, 분기별로 정규화해서 parquet에 저장했다고 치자. 여기까지만 해도 상당한 작업이다. 하지만 실제로 이 데이터를 분석에 쓰려는 순간 문제가 터진다.

  • 자산총계가 부채 + 자본과 안 맞는다
  • 어떤 종목은 2022년 3분기가 통째로 빠져 있다
  • 매출이 전기 대비 500배 뛴 회사가 있다
  • 은행주에서 유동자산을 조회하면 None이 나온다

이런 문제를 수작업으로 찾으려면, 2,700개 종목 × 20개 분기 × 수십 개 계정을 사람이 일일이 봐야 한다. 불가능하다. 검증은 자동화되어야 한다. 그것도 수집 파이프라인과 같은 수준의 엄밀함으로.

이 글은 재무 데이터 검증 파이프라인의 설계 원칙과 실전 전략을 다룬다. dartlab이 2,700개 종목의 재무제표를 수집하면서 실제로 적용하는 검증 체계를 바탕으로, 어디서 무엇을 어떻게 검사해야 데이터를 믿고 쓸 수 있는지를 정리한다.

데이터 품질 검증 3계층 구조


데이터 품질 검증이 선택이 아닌 이유

재무 데이터는 다른 데이터와 다르다. 숫자 하나가 틀리면 분석 전체가 무너진다.

PER을 계산한다고 하자. 순이익이 100억인 회사의 PER이 10이면 저평가일 수 있다. 그런데 매핑 오류로 순이익이 10억으로 잡히면 PER이 100이 된다. 이 회사는 고평가 종목으로 분류된다. 투자 판단이 정반대로 뒤집힌다.

문제는 이런 오류가 조용히 발생한다는 점이다. 에러를 던지지 않는다. 프로그램이 멈추지 않는다. 그냥 틀린 숫자가 올바른 숫자인 것처럼 흘러간다. 오류가 드러나는 건 몇 달 뒤 누군가 “이 숫자 이상한데?”라고 물어볼 때다.

재무 데이터에서 품질 문제가 생기는 3가지 단계

수집 단계: API 호출 실패, 네트워크 타임아웃, 정정공시 미반영, 파일 깨짐. 이건 비교적 잡기 쉽다. HTTP 상태 코드나 파일 크기만 봐도 된다.

매핑 단계: XBRL 계정 매핑에서 다뤘듯이, 회사마다 다른 계정명을 표준 snakeId로 변환하는 과정이다. 여기서 오매핑(wrong mapping)이 발생하면 숫자 자체는 존재하지만 의미가 달라진다. 유동자산이 비유동자산 자리에 들어가거나, 영업이익이 영업외이익으로 잡힌다.

정규화 단계: 누적 → standalone 변환, CFS/OFS 우선순위 처리, 분기 차분 계산. 로직 한 줄이 틀리면 모든 종목의 모든 분기가 체계적으로 잘못된다. 이런 종류의 오류는 패턴이 일관적이라 발견하기 더 어렵다.

검증 파이프라인은 이 세 단계 각각에 맞는 검사를 설계해야 한다.


3계층 검증 아키텍처

검증을 한 덩어리로 만들면 관리가 안 된다. 검증 규칙이 수십 개가 되면 어떤 규칙이 어떤 문제를 잡는 건지 알 수 없어진다. 대신 레이어를 분리한다.

Layer 1: 구조 검증 (Structural Validation)

가장 기본적인 검증이다. 데이터가 물리적으로 올바른 형태인지 확인한다. 이 레이어를 통과하지 못하면 이후 검증은 의미 없다.

스키마 검사: 기대하는 컬럼이 모두 존재하는가? bsns_year, sj_div, account_id, thstrm_amount 같은 필수 컬럼이 빠져 있으면 파이프라인 자체가 작동하지 않는다.

REQUIRED_COLUMNS = [
    "bsns_year", "sj_div", "account_id",
    "account_nm", "thstrm_amount", "frmtrm_amount"
]

for col in REQUIRED_COLUMNS:
    assert col in df.columns, f"Missing column: {col}"

타입 검사: 금액 컬럼이 문자열로 들어오는 경우가 있다. DART API는 숫자를 문자열로 반환하기 때문에, 파싱 과정에서 형변환이 빠지면 “1000000”이 문자열 그대로 남는다. 이걸 정렬하면 “1000000”이 “999999” 뒤에 온다.

null 비율 검사: 전체 행의 50% 이상이 null인 컬럼은 뭔가 잘못됐을 가능성이 높다. 정상적인 재무제표에서 금액이 절반 이상 비어 있을 이유가 없다.

행 수 검사: BS, IS, CF 각 재무제표별로 최소 행 수를 기대할 수 있다. BS가 3행이면 자산총계, 부채총계, 자본총계뿐이라는 뜻이고, 세부 항목이 전부 누락된 것이다.

검사 항목검사 기준실패 시 의미
필수 컬럼 존재REQUIRED_COLUMNS 전부 있어야 함수집/파싱 실패
금액 타입int64 또는 float64형변환 누락
null 비율금액 컬럼 null < 50%데이터 소스 문제
최소 행 수BS ≥ 10, IS ≥ 5, CF ≥ 5세부 항목 누락
중복 행(year, quarter, account_id) 유니크정정공시 중복

Layer 2: 회계 항등식 검증 (Accounting Identity Checks)

재무제표에는 반드시 성립해야 하는 수학적 관계가 있다. 이걸 검증하면 매핑 오류의 대부분을 잡을 수 있다.

BS 항등식: 자산총계 = 부채총계 + 자본총계. 이건 회계의 가장 기본적인 원칙이다. 이 등식이 안 맞으면 세 항목 중 최소 하나가 잘못 매핑된 것이다.

IS 관계: 매출액 - 매출원가 = 매출총이익. 매출총이익 - 판관비 = 영업이익. 이 관계가 깨지면 수익/비용 매핑에 문제가 있다.

CF 관계: 영업활동 + 투자활동 + 재무활동 = 현금 순변동. 기초 현금 + 순변동 = 기말 현금.

# BS 항등식 검증
total_assets = get("total_assets")
total_liabilities = get("total_liabilities")
total_equity = get("equity")

if total_assets and total_liabilities and total_equity:
    diff = abs(total_assets - (total_liabilities + total_equity))
    ratio = diff / abs(total_assets) if total_assets != 0 else float("inf")
    assert ratio < 0.001, f"BS identity failed: diff ratio = {ratio:.6f}"

이 레이어가 강력한 이유는, 잘못된 매핑이 우연히 항등식을 만족시킬 확률이 거의 0이기 때문이다. 자산총계가 10조인 회사에서 부채와 자본의 합이 정확히 10조가 되려면 매핑이 올바르거나, 아니면 기적적으로 우연이 맞아야 한다.

BS 항등식 검증이 오매핑을 잡아내는 과정

Layer 3: 통계/비즈니스 로직 검증 (Statistical & Business Logic Checks)

Layer 1과 2를 통과해도 남는 문제가 있다. 매핑은 맞지만 숫자 자체가 비정상인 경우다.

전기 대비 변동률: 매출이 전기 대비 100배 뛰었다면 십중팔구 단위 오류(원 vs 천원 vs 백만원)거나 정정공시 미반영이다.

부호 검사: 매출액이 음수인 경우. 영업이익은 음수(적자)일 수 있지만, 매출액이 음수인 건 극히 드물다. 자산총계가 음수인 건 불가능하다.

업종별 기대값: 은행은 매출원가가 없다. 보험사는 보험수익/보험비용 구조를 쓴다. 건설사는 진행률 기반 수익인식 때문에 계약자산/계약부채가 중요하다. 업종별로 “이 계정이 있어야 하는데 없다” 또는 “이 계정이 있으면 안 되는데 있다”를 검사한다.


BS 항등식 검증: 99.7% 정확도의 의미

dartlab은 2,700개 종목에 대해 BS 항등식을 검증했을 때 99.7% 정확도를 달성했다. 이 숫자의 의미를 정확히 이해하는 게 중요하다.

99.7%가 의미하는 것

2,700개 종목 × 평균 20개 분기 = 약 54,000개의 BS 검증 포인트가 있다. 99.7%면 약 160개 포인트에서 항등식이 깨진다는 뜻이다.

“깨진다”의 기준은 오차율 0.1% 이상이다. 즉 자산총계가 10조인 회사에서 부채 + 자본과의 차이가 100억 이상이면 실패로 본다. 금액이 작은 회사(자산 100억 미만)에서는 절대 오차 1억 원을 기준으로 쓴다.

0.3%가 의미하는 것

실패하는 케이스를 분석해 보면 대부분 아래 네 가지에 해당한다.

비지배지분 처리 차이: K-IFRS에서 자본총계(Equity)는 비지배지분(NCI)을 포함한다. 그런데 일부 회사는 지배기업 귀속 자본(EquityAttributableToOwnersOfParent)만 자본으로 보고하고 NCI를 따로 뺀다. 매핑에서 이 둘을 혼동하면 NCI만큼 차이가 난다.

별도재무제표(OFS) vs 연결재무제표(CFS) 혼재: 같은 분기에 CFS와 OFS가 모두 존재할 때, 행 단위로 CFS 우선 로직을 적용한다. 그런데 자산은 CFS에서, 부채는 OFS에서 가져오면 당연히 안 맞는다.

정정공시 미반영: 원본 공시 기준 BS는 맞지만, 이후 정정공시에서 수정된 숫자가 반영되지 않으면 구버전 숫자와 신버전 숫자가 섞인다.

확장 택소노미 오매핑: 회사 고유 XBRL 태그가 표준 계정으로 잘못 매핑된 경우. 예를 들어 dart_OtherCapitalSurplus를 자본잉여금이 아닌 다른 항목으로 잡으면 자본총계가 달라진다.

검증 실패 시 대응 전략

오차 범위원인 가능성대응
< 0.01%반올림 차이정상으로 처리
0.01% ~ 0.1%비지배지분 처리플래그 후 수동 확인
0.1% ~ 1%CFS/OFS 혼재, 정정공시우선순위 로직 재검토
> 1%오매핑 또는 데이터 소스 오류즉시 조사

핵심은 항등식 검증이 매핑 품질의 프록시라는 것이다. 항등식이 맞으면 최소한 BS의 3대 항목이 올바르게 매핑됐다는 증거가 된다. 항등식이 틀리면 반드시 원인을 추적해야 한다.


시계열 연속성 검증

BS 항등식이 “한 시점의 정합성”을 검사한다면, 시계열 연속성은 “시점 간 정합성”을 검사한다.

누락 분기 탐지

정상적인 상장사라면 매 분기 재무제표를 제출한다. 2020Q1부터 2025Q4까지 24개 분기가 있어야 하는데 22개만 있다면, 빠진 2개 분기가 어디인지 찾아야 한다.

expected_quarters = generate_quarters("2020Q1", "2025Q4")  # 24개
actual_quarters = df["period"].unique()
missing = set(expected_quarters) - set(actual_quarters)

if missing:
    flag(f"Missing quarters: {sorted(missing)}")

빠진 분기가 있으면 원인은 대개 세 가지다.

  1. 수집 실패: API 호출이 실패했는데 재시도가 안 된 경우
  2. 공시 미제출: 실제로 재무제표를 제출하지 않은 경우 (상장폐지, 관리종목 등)
  3. 정정공시로 대체: 원본 공시가 삭제되고 정정본만 남았는데, 정정본 수집 로직이 빠진 경우

급변 탐지 (Spike Detection)

전기 대비 변동률이 비정상적으로 큰 경우를 잡아낸다.

# 전기 대비 변동률 계산
for account in key_accounts:
    series = get_timeseries(account)
    for i in range(1, len(series)):
        prev, curr = series[i-1], series[i]
        if prev != 0:
            change_ratio = abs(curr / prev)
            if change_ratio > 100:  # 100배 이상 변동
                flag(f"{account}: {change_ratio:.0f}x change at {periods[i]}")

100배 이상 변동은 거의 확실히 오류다. 10배 이상은 의심 대상이다. 다만 업종에 따라 임계값이 달라야 한다. 바이오 벤처는 임상 성공 시 매출이 0에서 수백억으로 뛸 수 있고, 건설사는 대형 프로젝트 수주에 따라 수주잔고가 급변할 수 있다.

부호 전환 탐지 (Sign Flip)

자산총계가 양수였다가 음수로 바뀌는 건 불가능하다. 매출도 마찬가지다. 부호가 바뀌는 계정은 제한적이다.

계정부호 전환 허용 여부비고
자산총계절대 불가항상 양수
매출액불가극히 드문 예외 존재
영업이익허용흑자 → 적자 전환 가능
순이익허용흑자 → 적자 전환 가능
영업활동현금흐름허용유출 전환 가능
부채총계불가항상 양수 (0 가능)

시계열 연속성 검증 — 정상 vs 이상 패턴


재무제표 간 교차 검증

지금까지는 하나의 재무제표 안에서 검증했다. 하지만 BS, IS, CF는 서로 연결되어 있다. 이 연결고리를 검증하면 단일 재무제표 검증으로는 잡을 수 없는 오류를 발견할 수 있다.

IS 순이익 → BS 이익잉여금 변동

IS의 당기순이익은 BS의 이익잉여금 변동과 연결된다. 정확히 일치하지는 않는다. 배당, 자사주, 기타 자본 변동이 있기 때문이다. 하지만 순이익과 이익잉여금 변동의 차이가 순이익의 50%를 넘으면 뭔가 잘못됐을 가능성이 높다.

net_income = IS.get("net_income")  # 당기순이익
retained_prev = BS_prev.get("retained_earnings")  # 전기 이익잉여금
retained_curr = BS_curr.get("retained_earnings")  # 당기 이익잉여금

if all(v is not None for v in [net_income, retained_prev, retained_curr]):
    retained_change = retained_curr - retained_prev
    diff = abs(net_income - retained_change)
    if net_income != 0 and diff / abs(net_income) > 0.5:
        flag("IS net_income vs BS retained_earnings mismatch")

CF 기말 현금 → BS 현금성자산

현금흐름표의 기말 현금및현금성자산은 BS의 현금및현금성자산과 일치해야 한다. 이것은 거의 정확히 맞아야 하는 관계다.

cf_ending_cash = CF.get("cash_end")
bs_cash = BS.get("cash_and_equivalents")

if cf_ending_cash and bs_cash:
    diff = abs(cf_ending_cash - bs_cash)
    if diff > 100_000_000:  # 1억 원 이상 차이
        flag("CF ending cash vs BS cash mismatch")

교차 검증이 잡아내는 오류 유형

교차 검증정상 관계위반 시 원인
IS 순이익 ↔ BS 이익잉여금방향 일치 (± 배당/자본변동)순이익 또는 이익잉여금 오매핑
CF 기말현금 ↔ BS 현금정확히 일치현금 계정 매핑 오류
IS 매출 ↔ CF 영업현금흐름부호 같은 방향 (대체로)매출 또는 CF 오매핑
BS 유동자산 ↔ BS 자산총계유동자산 ≤ 자산총계유동/비유동 분류 오류

업종별 검증 규칙

모든 회사에 같은 검증 규칙을 적용하면 안 된다. 업종마다 재무제표 구조가 근본적으로 다르기 때문이다.

은행/금융업

은행의 재무제표에는 일반 제조업의 핵심 계정 상당수가 없다.

  • 매출액(revenue): 은행은 이자수익/비이자수익으로 나뉜다. 전통적인 의미의 “매출”이 없다
  • 매출원가(cost_of_goods_sold): 없다. 대신 이자비용이 있다
  • 유동자산/비유동자산 구분: 은행은 유동성 기준으로 자산을 분류하지 않는다
  • 재고자산: 없다 (금융상품이 사실상의 재고)

따라서 은행에 대해 “매출원가가 없으니 오류”라고 플래그를 달면 오탐(false positive)이 된다. 업종 필터가 필수다.

if industry == "bank":
    skip_checks = ["revenue_exists", "cogs_exists", "current_asset_check"]
    add_checks = ["interest_income_exists", "net_interest_margin_range"]

보험업

보험사는 보험수익/보험비용 구조를 쓴다. K-IFRS 17 도입 이후 보험계약부채의 구조가 크게 바뀌었기 때문에, 2023년 이전/이후의 검증 규칙이 달라야 한다.

건설업

건설사는 진행률 기반 수익인식 때문에 계약자산/계약부채가 핵심이다. 매출과 현금흐름의 괴리가 크고, 공사손실충당부채가 있는지 확인하는 것이 업종 특화 검증이다.

바이오/제약

R&D 단계 회사는 매출이 0이거나 극히 적을 수 있다. 판관비 중 연구개발비 비중이 매우 높다. 이 회사에 “매출 대비 판관비 비율이 비정상”이라고 플래그를 달면 오탐이다.

업종별 검증 규칙이 달라지는 이유

업종별 검증 비교 체크리스트

검증 항목제조업은행보험건설바이오
매출액 존재필수N/AN/A필수선택
매출원가 존재필수N/AN/A필수선택
유동/비유동 분류필수N/AN/A필수필수
BS 항등식필수필수필수필수필수
이자수익/비용선택필수필수선택선택
보험수익/비용N/AN/A필수N/AN/A
계약자산/부채선택N/AN/A필수N/A
R&D 비중 검사선택N/AN/AN/A필수
매출 대비 판관비< 50%N/AN/A< 30%제한 없음

이상치 자동 탐지

Layer 3의 핵심은 통계적 이상치 탐지다. 사람이 규칙을 하나하나 정의하는 데는 한계가 있다. 대신 데이터 자체에서 이상한 패턴을 찾아내는 방법이 필요하다.

Z-Score 방식

같은 업종 내에서 특정 지표의 평균과 표준편차를 구하고, 개별 회사의 값이 평균에서 얼마나 떨어져 있는지를 계산한다.

# 업종별 Z-Score 계산
for industry_group in industries:
    values = get_metric_values(industry_group, "operating_margin")
    mean = average(values)
    std = standard_deviation(values)

    for company in industry_group:
        z = (company.operating_margin - mean) / std if std > 0 else 0
        if abs(z) > 3:  # 3시그마 이상
            flag(f"{company.name}: operating_margin z-score = {z:.2f}")

Z-Score > 3이면 상위 0.13%에 해당하는 극단값이다. 이건 “대단히 뛰어난 회사”이거나 “데이터 오류”다. 둘 다 확인할 가치가 있다.

IQR 방식

사분위수 범위(IQR)를 쓰면 극단적인 이상치에 덜 민감한 기준을 세울 수 있다.

Q1 = percentile(values, 25)
Q3 = percentile(values, 75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR

outliers = [v for v in values if v < lower or v > upper]

Z-Score는 정규분포를 가정하지만 재무 지표는 대부분 비대칭 분포이므로, IQR이 더 안정적인 결과를 줄 때가 많다.

두 방식의 비교

방식장점단점적합한 지표
Z-Score해석이 직관적비대칭 분포에 약함수익성 비율, ROE
IQR이상치에 강건임계값 조정 필요매출 변동률, 부채비율
전기 대비 변동률시계열 이상 탐지신규 상장사 불가모든 금액 항목

검증 파이프라인 전체 구조

개별 검증 규칙이 아무리 좋아도, 이를 파이프라인으로 조립하지 않으면 운영할 수 없다. 검증은 수집 직후 자동으로 돌아야 하고, 결과는 리포트로 남아야 한다.

4단계 파이프라인

1단계: 종목별 검증 (Per-Company)

  • 각 종목의 데이터를 독립적으로 검증
  • Layer 1 구조 검사 + Layer 2 항등식 검사
  • 가장 먼저 돌리고, 여기서 실패하면 이후 단계 스킵

2단계: 교차 종목 검증 (Cross-Company)

  • 같은 업종 내 종목 간 비교
  • 업종 평균 대비 이상치 탐지
  • 업종 분류 자체가 맞는지도 간접 확인

3단계: 시계열 검증 (Time Series)

  • 분기 누락, 급변, 부호 전환 탐지
  • 전기 대비 변동률 + 추세 이탈 검사
  • standalone 변환 로직의 정확성 간접 검증

4단계: 이상치 보고서 (Anomaly Report)

  • 전 단계의 플래그를 종합
  • 심각도별 분류: ERROR / WARNING / INFO
  • 종목별, 계정별, 기간별 피벗 테이블 생성

검증 파이프라인 전체 흐름과 의사결정 분기

파이프라인 의사결정: Flag vs Reject vs Auto-Correct

검증 실패가 발생했을 때 어떻게 대응하느냐가 중요하다. 세 가지 전략이 있고, 상황에 따라 달리 적용한다.

Flag (플래그): 데이터는 그대로 두고, 문제가 있다는 표시만 남긴다. 분석자가 나중에 확인한다. 대부분의 검증 실패는 여기에 해당한다.

Reject (거부): 해당 데이터를 아예 사용하지 않는다. BS 항등식이 1% 이상 깨지는 경우처럼, 데이터를 쓰면 분석이 확실히 잘못되는 경우에만 적용한다.

Auto-Correct (자동 수정): 규칙 기반으로 자동 보정한다. 극히 제한적으로만 사용해야 한다. 예를 들어 단위 오류(천원 → 원)가 확실한 경우, 또는 CFS/OFS 우선순위가 명백한 경우.

전략적용 조건위험도예시
Flag원인이 불확실한 이상낮음전기 대비 매출 10배 변동
Reject사용 시 분석 오류 확실중간BS 항등식 1% 이상 불일치
Auto-Correct수정 규칙이 명확하고 검증 가능높음천원→원 단위 보정

자동 수정은 편리하지만 위험하다. 잘못된 자동 수정은 오류를 감추고 더 큰 문제를 만든다. 원칙: 자동 수정 전과 후를 모두 기록하고, 수정 이력을 추적 가능하게 남긴다.


모니터링 대시보드 설계

검증을 한 번 돌리는 것만으로는 부족하다. 시간에 따라 품질이 어떻게 변하는지 추적해야 한다.

핵심 모니터링 지표

매핑률 추이: 전체 계정 중 표준 snakeId로 매핑된 비율. XBRL 계정 매핑에서 다뤘듯이, dartlab은 97% 이상을 목표로 한다. 이 수치가 떨어지면 새로운 계정명이 등장했거나, 매핑 로직에 버그가 생긴 것이다.

BS 항등식 통과율: 전 종목 대비 BS 항등식이 0.1% 이내로 맞는 종목의 비율. 현재 99.7%. 이 수치가 떨어지면 매핑이나 정규화 로직에 퇴행(regression)이 발생한 것이다.

업종별 오류율: 특정 업종에서 오류가 집중되면, 그 업종의 특수 계정 처리에 문제가 있을 가능성이 높다. 금융업 오류율이 갑자기 올라가면 은행 계정 매핑을 점검해야 한다.

분기별 누락률: 최신 분기의 데이터가 얼마나 완전히 수집됐는지. 공시 마감 직후에는 누락이 많고, 시간이 지나면 채워진다. 이 패턴을 추적하면 수집 파이프라인의 안정성을 판단할 수 있다.

모니터링 테이블 예시

지표2025Q32025Q42026Q1추세
매핑률97.02%97.05%97.07%안정
BS 항등식 통과99.68%99.71%99.70%안정
분기 누락 종목12815주시
Layer 3 이상치453852주시
자동 수정 건수321감소

검증 유형 종합 비교

전체 검증 유형을 한 표로 정리하면 아래와 같다.

계층검증 유형잡아내는 오류자동화 난이도오탐률
L1스키마 검사수집 실패, 파싱 오류쉬움매우 낮음
L1타입 검사형변환 누락쉬움거의 없음
L1null 비율데이터 소스 문제쉬움낮음
L2BS 항등식자산/부채/자본 오매핑보통매우 낮음
L2IS 관계수익/비용 오매핑보통낮음
L2CF 관계현금흐름 오매핑보통낮음
L2교차 재무제표제표 간 불일치보통낮음
L3전기 대비 변동단위 오류, 급변쉬움중간
L3부호 전환매핑 오류, 입력 오류쉬움낮음
L3Z-Score 이상치극단값, 업종 이상보통중간
L3업종별 기대값업종 특화 오류어려움중간

실전에서 만난 함정들

검증 파이프라인을 운영하면서 이론과 다른 현실을 여러 번 만났다. 몇 가지 사례를 공유한다.

함정 1: “정상인데 비정상으로 보이는” 케이스

한 반도체 회사의 매출이 전기 대비 30배 뛴 경우. 데이터 오류인 줄 알고 조사했더니, 실제로 사업부 매각 후 신사업 진입으로 매출 구조가 완전히 바뀐 것이었다. 이런 경우를 오탐으로 처리하면 된다고 쉽게 생각할 수 있지만, 문제는 다음에 같은 패턴이 나왔을 때 진짜 오류인지 다시 확인해야 한다는 것이다.

함정 2: 연결/별도 혼재의 복잡함

XBRL 계정 매핑에서도 다뤘지만, CFS와 OFS가 같은 분기에 공존할 때의 처리는 생각보다 복잡하다. CFS 우선 로직을 행 단위로 적용하면, 어떤 행은 CFS에서, 어떤 행은 OFS에서 오는 혼재 상태가 된다. BS 항등식은 같은 재무제표 내에서 성립해야 하므로, 혼재된 상태에서는 의미가 없다.

함정 3: 천원 / 백만원 / 원 단위 혼재

DART는 기본적으로 “원” 단위로 데이터를 제공하지만, 일부 API는 “백만원” 단위다. 이걸 놓치면 모든 숫자가 백만 배 차이 난다. 문제는 이런 오류가 항등식 검증은 통과한다는 것이다. 모든 항목이 같은 비율로 틀리기 때문이다. 이런 경우는 교차 종목 비교에서만 잡힌다 — 같은 업종의 다른 회사와 비교하면 자산 규모가 비정상적으로 크거나 작다.

함정 4: 정정공시 타이밍

정정공시 파이프라인에서 다뤘듯이, 원본과 정정 사이에 시간차가 있다. 수집 시점에 따라 원본만 수집되거나, 정정본만 수집되거나, 둘 다 수집될 수 있다. 검증 결과도 수집 시점에 따라 달라진다. 주기적으로 전수 재검증을 돌려야 하는 이유다.


비교 체크리스트

확인 항목기준선위험 신호
BS 항등식 일치율> 99%< 95%
핵심 20개 계정 커버리지> 98%< 90%
분기 연속성 (gap 없음)100%1개 이상 누락
YoY 변동 100배 이상0건1건 이상
음수 자산총계0건1건 이상
금융업 별도 규칙 적용적용됨일괄 처리
이상치 플래그 비율< 2%> 5%

FAQ

Q. 검증은 얼마나 자주 돌려야 하나요?

수집 직후 자동으로 돌리는 것이 원칙이다. 새로운 분기 데이터가 들어올 때마다 Layer 1 + Layer 2를 즉시 검증한다. Layer 3 (통계 검증)은 업종 기준값이 바뀌므로 주 1회 정도 전수 검증을 돌리면 충분하다.

Q. 검증 통과율 100%를 목표로 해야 하나요?

아니다. 99.7% 같은 수치는 현실적으로 매우 좋은 수준이다. 100%를 달성하려면 예외 처리가 기하급수적으로 복잡해지고, 오히려 과적합(overfitting)된 규칙이 새로운 데이터에서 문제를 일으킨다. 핵심은 실패한 0.3%가 무엇인지 알고 있는 것이다.

Q. 오탐(false positive)이 너무 많으면 어떻게 하나요?

오탐이 많으면 검증 결과를 무시하게 된다. 그러면 진짜 오류도 놓친다. 오탐을 줄이는 가장 효과적인 방법은 업종별 검증 규칙 분리다. 은행에 제조업 규칙을 적용하지 않는 것만으로도 오탐이 절반 이상 줄어든다.

Q. 검증 파이프라인 구축에 어느 정도 시간이 걸리나요?

Layer 1 (구조 검증)은 하루면 된다. Layer 2 (항등식 검증)은 매핑 체계가 잡혀 있다면 2~3일이면 된다. Layer 3 (통계 검증)은 업종 분류와 기준값 설정이 필요해서 1~2주가 걸린다. 모니터링 대시보드까지 포함하면 전체 1개월 정도를 잡는 게 현실적이다.

Q. 소규모 프로젝트에서도 이런 검증이 필요한가요?

종목 수가 적으면 수작업으로 확인할 수 있다. 하지만 10종목만 넘어가도 자동화하는 게 낫다. BS 항등식 검사 하나만 돌려도 매핑 오류의 80%는 잡힌다. 최소한의 투자로 최대 효과를 보려면 Layer 2부터 시작하라.


시리즈 다른 글

이 글은 data-automation 시리즈의 일부다. 수집부터 검증까지의 전체 흐름은 아래 글에서 이어진다.


출처

  • K-IFRS 재무보고 기준서: 한국회계기준원 (KASB) — 재무제표 작성과 항등식의 근거
  • OpenDART API 명세: 금융감독원 전자공시시스템 — 재무 데이터 수집 원천
  • XBRL International: eXtensible Business Reporting Language 표준 — 계정 택소노미 근거
  • WICS 업종분류: FnGuide — 업종별 검증 규칙 분류 기준
  • dartlab 프로젝트: github.com/eddm-ai/dartlab — BS 항등식 99.7%, 매핑률 97%+ 검증 실측 데이터

한 줄 정리

재무 데이터 검증은 구조 검사 → 회계 항등식 → 통계 이상치의 3계층으로 쌓되, BS 항등식 하나만 돌려도 매핑 오류의 대부분을 잡을 수 있다.

같은 시리즈에서 이어 읽기
DartLab

같은 카테고리에서 더 읽기

DartLab은 데이터 자동화 카테고리 안에서 글이 서로 이어지도록 설계합니다. 다음 글로 넘어가며 구조와 맥락을 같이 쌓는 방식입니다.

DartLab Product

이 글의 판단을 실제 데이터 흐름으로 옮기기

DartLab은 전자공시를 읽는 법을 코드와 데이터로 연결하기 위해 만든 제품입니다. 사업보고서 텍스트, 재무 시계열, 정기보고서 데이터를 한 흐름에서 다루도록 설계했습니다.