13.4.2 총계 검증(Total Aggregation): 개별 품목 합계와 문서 전체 총액(Subtotal/Total) 일치 여부 확인
앞선 13.4.1절의 혹독한 과정을 통해 우리는 문서 내의 가장 작은 세포 단위 연산인 개별 품목(Line Item)들의 대수식(단가 \times 수량 == 합계) 논리를 무사히 검증해 냈다. 이 수많은 개별 세포들이 모두 수학적으로 건강하다고 판별되면, 이제 오라클 아키텍처는 시야를 단일 품목에서 문서 전체의 숲으로 넓혀, 시스템의 거시적 균형(Macro Balance)을 타격하고 확인하는 ‘총계 검증(Total Aggregation)’ 레이어로 진입하게 된다.
어떤 복잡한 글로벌 송장(Invoice)에서 15개의 유효한 B2B 구매 품목이 추출되었다고 가정해 보자. LLM이 놀라운 주의력(Attention)으로 15개 개별 품목의 테이블 데이터는 완벽하게 인식하고 대수학을 통과시켜 냈음에도 불구하고, 정작 문서 제일 하단에 위치한 굵은 글씨의 ’최종 총 결제 금액(Total Amount)’을 추출할 때는 주변의 엉뚱한 노이즈 숫자(예: 과거 미납금 누계, 할인율 코드, 송장 번호)를 잘못 참조하여 치명적인 오답을 추출하는 ‘위상적 환각(Topological Hallucination)’ 현상은 결코 드물지 않게 일어난다.
이러한 문서 전체 레벨의 파괴적 논리 오류를 방어하기 위해, 엔터프라이즈 2단계 오라클은 추출된 배열 속 개별 품목들의 합계값(LineTotal_n)들을 모두 sum() 누적 합산(Roll-up)한 뒤, 이 연산된 스칼라 값이 LLM이 단일 필드로 뚝 떼어 추출해 낸 sub_total(공급가액) 및 total_amount(총청구액)와 소수점 끝자리까지 오차 없이 일치하는지 마지막 대수학적 심판을 내려야만 한다.
1. 전역 누적합(Global Roll-up) 방정식 검증 설계
이 거시적인 총계 검증은 개별 아이템 모델을 넘어선 전체 뷰(View)가 필요하므로, Pydantic 메타 모델의 최상단(Root) 팩토리 레벨인 InvoiceExtract 메인 클래스 객체에 @model_validator 데코레이터를 부착하여 수행된다. 앞선 단계와 마찬가지로 이 과정 역시 IEEE 754 부동소수점 오차 방지를 위한 Decimal 강제 연산이 필수적으로 요구된다.
from pydantic import BaseModel, model_validator
from decimal import Decimal, ROUND_HALF_UP
class TotalAggregationOracle(BaseModel):
items: list[dict] # 13.4.1절의 개별 단가 검증을 모두 통과하고 올라온 무결점 품목 배열
sub_total: float
tax_amount: float
total_amount: float
@model_validator(mode='after')
def verify_global_aggregation_logic(self):
"""
문서의 하위 품목 누적합이 문서의 전역(Global) 토탈과 기계적으로 일치하는지 검열한다.
"""
# 1. 하위 세포들의 합산: 정제된 개별 품목 합계값의 누적합(Sum) 계산
calculated_sub_total = Decimal('0.0')
for item in self.items:
# item.line_total 은 이미 직전 검문소에서 환각이 아님이 증명된 안전한 숫자다.
calculated_sub_total += Decimal(str(item.get('line_total', 0.0)))
# 2. 문서 레벨의 1차 회계 퍼즐: 계산된 누적(Sum)이 추출된 sub_total과 일치하는가?
extracted_sub = Decimal(str(self.sub_total))
if calculated_sub_total != extracted_sub:
raise ValueError(
f"[거시 총계 검증 실패] 품목 합산 누적액({calculated_sub_total})과 "
f"LLM이 추출한 문서 명시 공급가액({extracted_sub})이 논리적으로 대립합니다. 환각 의심."
)
# 3. 문서 레벨의 최종 퍼즐(Grand Total): Subtotal + Tax = Total 방정식 최종 검증
extracted_tax = Decimal(str(self.tax_amount))
extracted_total = Decimal(str(self.total_amount))
calculated_grand_total = calculated_sub_total + extracted_tax
if calculated_grand_total != extracted_total:
raise ValueError(
f"[최종 청구액 무결성 파단] (공급가액 + 세금)의 산술 연산 결과({calculated_grand_total})가 "
f"문서에서 추출된 최종 청구 결제액({extracted_total})과 모순되어 스키마를 롤백합니다."
)
return self # 진실로 증명된 완전 무결한 객체만을 DB 커밋 레이어로 반환한다.
2. 결측치(Missing Values)와 회계 연역적 역추론(Reverse Engineering)
이 촘촘한 총계 검증 오라클 구조는 파이프라인에 단순히 익셉션(Exception)을 격발시키는 소극적 방어막을 넘어, 시스템 스스로 데이터를 복원하는 강력한 **‘역연산(Reverse Engineering) 복구 엔진’**으로도 전략적으로 활용될 수 있다.
만약 감열지 영수증의 하단 구석이 잉크 번짐이나 물리적으로 심하게 훼손되어 있어서, LLM이 컴퓨터 비전을 통해 특정 tax_amount(세금) 값을 추출해 내지 못하고 null을 뱉어낼 위기에 처했다고 가정해 보자.
훌륭하게 설계된 오라클은 이때 멍청하게 파이프라인을 셧다운(Shutdown) 시키는 대신, 이미 다른 부분에서 완벽하게 추출 및 검증이 끝난 텐서 값인 total_amount와 sub_total을 대수학적으로 재활용한다. 시스템은 Tax = Total - Subtotal 이라는 절대 불변의 회계 연역적 추론 공식을 작동시켜, 인간 개발자의 개입 없이 오라클 스스로 그 훼손된 빈칸(Missing Value)의 값을 안전하게 계산하여 메워 넣도록 MLOps 파이프라인을 고도화할 수 있다.
총계 검증(Total Aggregation)의 아키텍처 과정은, 마치 부서지고 흩어진 의미 없는 숫자(Token) 조각들을 긁어 모아 완벽하게 맞물린 하나의 회계 입방체(Cube)를 조립해 내는 숭고한 공학적 과정과 같다.
개별 품목의 합이 거시적인 전체 공급가액이 되고,そこに 세금이 한 치 오차 없이 더해져 단 1동전의 오차도 없는 최종 청구액(Grand Total)이라는 완벽한 아키텍처 결론에 연산 도달할 때, 우리는 이 지저분하고 훼손된 비정형 문서가 비로소 인간 회계 사관(Auditor)을 즉각 통과할 수 있는 완벽한 RDBMS 레코드 자격을 갖췄다고 당당히 선언할 수 있다.