13.5.3 데이터베이스 내 발주 번호(PO Number) 존재 여부 및 상태 조회
엔터프라이즈 B2B(기업 간) 거래의 심장부 역할을 하는 **발주 번호(Purchase Order Number, PO Number)**는, 단순한 문서 번호를 넘어서 기업이 특정 벤더에게 정해진 금액 한도 내에서 물품을 구매하겠다고 사전에 회계적으로 약속한 엄청난 권한을 지닌 고유 식별자(Unique Identifier)다.
비전 LLM이 공급자 송장(Invoice) 픽셀에서 PO-2023-9999라는 발주 번호를 완벽하게 파싱하여 앞선 1단계(타입 무결성), 2단계(산술 무결성) 오라클을 무사히 통과했다 하더라도 안심해서는 안 된다.
만약 문서에 인쇄된 그 번호 자체가 누군가에 의해 임의로 조작되었거나, 혹은 과거에 이미 한도 초과로 무효화(Void) 및 마감(Closed) 처리된 죽은 번호라면, 이 추출된 데이터 트랜잭션이 파이프라인의 끝에서 결제 시스템과 맞닿는 순간 돌이킬 수 없는 수백만 원 단위의 회계 부정(Accounting Fraud)을 초래하게 됨을 아키텍트는 뼈저리게 명심해야 한다.
따라서 3단계 외부 지식 오라클 베이스캠프에서는, 메모리에 갓 추출된 이 PO Number를 쥐고 비동기로 사내 조달(Procurement) 데이터베이스 망에 긴급 쿼리를 날려, 해당 번호의 단순한 ’존재 여부(Existence)’를 확인하는 수준을 아득히 뛰어넘어, 그 번호가 지닌 **‘현재 시스템상의 비즈니스 상태(State Lifecycle)’**까지 완전히 해부하고 감시해야만 한다.
1. PO Number 상태 머신(State Machine) 검증 오라클 파이프라인
이 파이프라인 단계에서는 단순히 데이터베이스에 핑을 날려 SELECT count(*) FROM po_table WHERE po_number = 'PO-2023-9999' 가 1 이상인지 조회하는 얕은 쿼리로는 턱없이 부족하다.
3단계 오라클은 조회된 발주 번호가 현재 시스템 상태 머신 상에서 **사용 가능한 상태(OPEN / PARTIALLY_RECEIVED)**인지 철저히 확인하고, 취소되거나 한도가 소진되어 이미 차갑게 식어버린 마감 상태(CLOSED / REJECTED)라면 즉각 예외를 공중으로 격발해야만 한다.
from pydantic import BaseModel, model_validator
import httpx
class PurchaseOrderStateIntegrityOracle(BaseModel):
invoice_number: str # 벤더가 보내온 청구서 문서 번호
po_number: str # LLM이 읽어낸 우리 회사의 발주 번호 (Foreign Key)
invoice_total_amount: float # LLM이 추출하고 2단계에서 연산 무결성을 인정받은 총금액
# 사내 조달(Procurement) MSA 컴포넌트의 PO 상태 조회 엔드포인트
_PO_SYSTEM_API = "http://internal-procurement.corp/api/v1/po/status"
@model_validator(mode='after')
def verify_po_lifecycle_and_budget(self):
"""
사내 조달 시스템망에 쿼리를 던져 PO Number의 생존 상태와 예산 한도를 입체적으로 검증한다.
"""
response = httpx.get(f"{self._PO_SYSTEM_API}/{self.po_number}", timeout=3.0)
# 1. 실재성 일차 검증 (고아 레코드 방어)
if response.status_code == 404:
raise ValueError(
f"[마스터 PO 룩업 실패] 문서에서 도출된 B2B 발주 번호({self.po_number})는 "
f"당사의 조달 마스터 시스템에 등재된 적조차 없는 철저한 허위(Fake) 번호입니다."
)
po_data = response.json()
# 2. 상태 머신(State Machine) 생명주기 검증: 권한이 닫혀있는가 열려있는가?
current_status = po_data.get("status")
if current_status not in ["OPEN", "APPROVED", "PARTIAL_RECEIVED"]:
raise ValueError(
f"[PO 라이프사이클 위반] 발주 번호({self.po_number})의 현재 백엔드 상태가 "
f"이미 마감된 '{current_status}' 모드이므로 이 청구서를 결합(Mapping)할 수 없습니다."
)
# 3. 예산 한도(Budget Limit) 교차 검증 (크로스 체크)
remaining_balance = float(po_data.get("remaining_balance", 0.0))
if self.invoice_total_amount > remaining_balance:
raise ValueError(
f"[한도 초과 리스크 검출] 이번에 넘어온 청구 금액 총액({self.invoice_total_amount})이 "
f"해당 PO 시스템의 남은 승인 잔액({remaining_balance})을 폭력적으로 초과했습니다. "
f"어텐션 환각 혹은 벤더 측의 과다 청구이므로 결제 롤백을 수행합니다."
)
return self
2. 멱등성(Idempotency)의 절대 방어: 악의적 이중 청구(Double Billing) 차단
이 PO 룩업 오라클이 수행하는 눈에 보이지 않는 또 다른 가장 위대한 아키텍처적 가치는 바로, 분산 시스템의 가장 중요한 덕목인 ‘멱등성(Idempotency)’ 확보를 통한 파괴적인 이중 청구(Double Billing) 원천 차단에 있다.
과거 6개월 전에 이미 전표가 처리되고 결제가 완료된 영수증이, 인간 담당자의 단순 실수나 혹은 공급자의 악의적인 자금 횡령 목적으로 스캐너를 통해 우리 파이프라인에 다시 한 장 업로드되어 흘러들어왔다고 극한 가정을 해보자.
아무리 똑똑한 최신 LLM일지라도 데이터 추출 관점에서는 “이전과 100% 동일하게 완벽한” 텍스트 덩어리를 예쁘게 뽑아낼 것이고, 이는 1, 2단계 논리 오라클을 콧노래를 부르며 유유히 프리패스 통과하게 된다.
오직 이 **‘3단계 마스터 데이터 조회 오라클’**만이, 넘겨받은 invoice_number와 po_number 의 튜플 결합이 우리 ERP 원장(Ledger)에 이미 PAID(지급 완료) 상태로 낙인이 찍혀있음을 데이터베이스에서 귀신같이 찾아내어 트랜잭션을 하드 블로킹(Blocking) 시킨다.
결국 AI 모델 자체의 ’기억상실’이라는 맹점을 사내 데이터베이스라는 ’영구적인 지식의 앵커(Anchor)’로 묶어냄으로써, 시스템을 이중 지불이라는 재무적 참사로부터 멱등적으로 영원히 구원해 내는 것이다.