DART 재무데이터를 Parquet로 안전하게 이어 저장하는 법

재무데이터를 매번 전체 수집하면 호출량과 실행 시간이 늘고, 새 데이터만 기존 파일에 붙이면 중복과 스키마 변화가 쌓입니다. 증분 저장은 새 행을 받는 것보다 기존 파일을 깨뜨리지 않고 같은 기준으로 합치는 것이 핵심입니다.

이 글의 핵심

  • 회사·연도·보고서·연결 구분·재무제표·계정을 복합 기본키로 둡니다.
  • 접수번호가 더 최신인 행을 남기되 정정 이력 원본은 별도 보존합니다.
  • 자료형과 필수 열을 검사한 뒤 임시 파일을 완성합니다.
  • 검사가 끝난 파일만 os.replace로 원자적 교체합니다.
DART 재무데이터의 증분 저장과 원자적 교체

한 열짜리 기본키로는 부족합니다

같은 회사의 같은 계정도 사업연도, 보고서 종류, 연결·별도, 재무제표 구분에 따라 여러 행이 존재합니다.

TEXT
corp_code + bsns_year + reprt_code + fs_div + sj_div + account_id

계정명이 같아도 표준 계정 ID가 다를 수 있고, 회사가 자체 확장한 계정은 -표준계정코드 미사용-으로 들어올 수 있습니다. 이런 행은 이름만으로 합치지 말고 원본 순서·계정명·접수번호를 함께 보존합니다.

실행 코드

PYTHON
#!/usr/bin/env python3
"""DART 재무데이터를 중복 없이 검증한 뒤 Parquet로 원자적 교체한다."""

from __future__ import annotations

import os
from pathlib import Path

import pandas as pd


PRIMARY_KEY = ["corp_code", "bsns_year", "reprt_code", "fs_div", "sj_div", "account_id"]
REQUIRED_COLUMNS = PRIMARY_KEY + ["account_nm", "amount", "rcept_no"]


def validate(frame: pd.DataFrame) -> pd.DataFrame:
    missing = set(REQUIRED_COLUMNS) - set(frame.columns)
    if missing:
        raise ValueError(f"필수 열이 없습니다: {sorted(missing)}")
    checked = frame[REQUIRED_COLUMNS].copy()
    checked["amount"] = pd.to_numeric(checked["amount"], errors="raise").astype("Int64")
    checked = checked.sort_values(PRIMARY_KEY + ["rcept_no"])
    checked = checked.drop_duplicates(PRIMARY_KEY, keep="last")
    if checked.duplicated(PRIMARY_KEY).any():
        raise ValueError("기본키 중복을 제거하지 못했습니다.")
    return checked


def atomic_merge(new_rows: pd.DataFrame, output: Path) -> None:
    old_rows = pd.read_parquet(output) if output.exists() else pd.DataFrame(columns=REQUIRED_COLUMNS)
    merged = validate(pd.concat([old_rows, new_rows], ignore_index=True))
    output.parent.mkdir(parents=True, exist_ok=True)
    temporary = output.with_suffix(".tmp.parquet")
    merged.to_parquet(temporary, index=False)
    pd.read_parquet(temporary, columns=REQUIRED_COLUMNS)
    os.replace(temporary, output)


if __name__ == "__main__":
    print("수집한 DataFrame을 atomic_merge(new_rows, Path('data/dart_financials.parquet'))로 저장합니다.")

validate는 필수 열, 금액 자료형, 복합키 중복을 검사합니다. atomic_merge는 기존 파일과 새 행을 합친 뒤 임시 Parquet를 다시 읽어 보고 최종 경로로 교체합니다. 실행 중 중단돼도 검증 전 파일이 기존 파일을 덮지 않습니다.

정정공시 원본은 다른 계층에 둡니다

분석용 테이블은 기본키마다 최신 유효 값을 하나만 남기는 편이 편리합니다. 그러나 정정 전 값까지 지우면 과거 분석을 재현할 수 없습니다. 따라서 raw에는 접수번호별 원본, curated에는 최신 선택값, features에는 비율과 단일 분기 파생값을 두는 구조가 안전합니다.

저장 뒤 확인할 것

  1. 복합키 중복이 0개인지 확인합니다.
  2. 금액 열이 문자열로 되돌아가지 않았는지 확인합니다.
  3. 기존 행 수보다 줄었다면 정정·중복 제거 이유를 기록합니다.
  4. 임시 파일이 남았다면 이전 실행이 중단됐는지 확인합니다.
  5. 새 사업연도와 보고서 코드가 허용 목록 안에 있는지 확인합니다.

Parquet는 데이터 정제와 분석에 편리한 선택일 뿐 장기 보관 원본을 대신하지 않습니다. API 응답과 접수번호 이력을 함께 보관해야 결과가 바뀐 이유를 설명할 수 있습니다.