관점 에세이 · 2026-08-29
사람에게도 타입이 필요했다
그래프의 연결만으로는 읽히지 않는 법적 의미를 위해, 당사자 유형을 세법 온톨로지에 넣었다

2편에서 법은 조건문의 집합이 아니라 사건 그래프에 가깝다고 썼다. 관계가 맞아도 사건의 종류와 순서가 다르면 같은 조문을 적용할 수 없었다. 그래서 Relationship 옆에 Event, TransactionPattern, Time을 놓기 시작했다.
그때 자연스럽게 이런 질문도 따라왔다. 이 구조를 그래프 DB로 만들면 되지 않을까.
된다. Person, Asset, Transfer, Liability를 노드로 두고 부모 관계, 소유, 채무 인수, 참여 역할을 간선으로 연결하는 일은 Property Graph가 특히 잘한다.
(Person:A) -[:PARENT_OF]-> (Person:B)
│
└-[:PARTICIPATES { role: "donor" }]-> (Transfer:T1)
│
├-[:TRANSFERS]-> (Asset:House)
└-[:ASSUMES]-> (Liability:Debt)
문제는 그래프가 부족해서가 아니었다. 이 연결들이 세법에서 무엇을 뜻하는지, 그리고 그 뜻이 다른 규칙에도 어떻게 이어지는지를 어디에 둘 것인가의 문제였다.
그래프는 연결을, 온톨로지는 연결의 뜻을 맡는다
원본 사실에 A parentOf B만 있다고 하자. 사람이 읽으면 A가 B의 직계존속이고, 특정 세법 문맥에서는 친족 또는 특수관계인의 판단 재료가 될 수 있다고 이어서 이해한다. 컴퓨터는 그렇지 않다. 그 사이의 의미 사다리를 명시하지 않으면 parentOf는 이름이 붙은 간선 하나일 뿐이다.
parentOf
↓
linealAscendantOf
↓
relativeOf
↓
relatedPartyOf
Property Graph에서도 이 사다리는 표현할 수 있다. 다만 보통은 애플리케이션 코드나 Cypher 규칙으로 관계를 추가로 만들거나, 쿼리마다 관계를 함께 열거해야 한다. RDF/OWL에서는 관계의 포함 관계를 TBox/RBox에 선언할 수 있다. 그 지식은 특정 화면이나 쿼리의 구현이 아니라 온톨로지에 남는다.
그래프는 현실 사건의 연결 구조를 담고, 온톨로지는 그 연결이 법적 판단에서 어떤 범주와 요건으로 읽히는지를 담는다. 현재 프로젝트는 RDF/OWL을 의미 계층의 정본으로 쓰고 있다. Property Graph를 실행용 탐색 계층으로 함께 쓰는 것은 가능한 선택지지만, 아직 그렇게 배치한 사실은 아니다.
그런데 다음 구조적 구멍은 더 단순한 곳에 있었다. 사람을 담는 모델에 사람의 유형이 없었다.
처음에는 이상하지 않았다. 가족 자산운용 시스템이니 Person이 있고, 사람들 사이에는 부모·자녀·배우자 같은 Relationship이 있으면 된다고 생각했다. 하지만 이 모델은 조용히 한 가지를 가정하고 있었다.
모든 중요한 세법 규칙은 두 사람 사이의 관계에서 출발한다는 가정이었다.
그 가정은 틀렸다.
관계가 없다고 규칙도 없는 것은 아니었다
지방세법 제13조의2 제1항 제1호는 법인이 주택을 유상으로 취득하는 경우를 다룬다. 이 조문이 묻는 첫 질문은 양도인과 양수인이 가족인지, 특수관계인인지가 아니다. 취득자가 법인인가다.1
가상의 거래를 하나 생각해 보자.
Seller ──주택 매매──> Buyer Corp.
두 당사자 사이에는 등록된 관계가 없다. 가족도 아니고, 별도의 특수관계 사실도 입력하지 않았다.
기존 Resolver는 여기서 멈췄다. 두 사람 사이의 관계를 찾고, 관계가 없으면 후보 규칙 검색을 일찍 끝냈다. 관계 기반 규칙을 찾는 코드로서는 자연스러운 흐름이었다. 하지만 법인 주택 취득 중과처럼 상대방과 무관하게 취득자 자체의 법적 성격에서 시작하는 규칙은, 조문이 온톨로지에 등록돼 있어도 영영 검색되지 않았다.
규칙이 없었던 것이 아니다. 규칙으로 들어가는 문이 하나뿐이었던 것이다.
이 차이는 작아 보이지만 중요하다. 검색 결과가 비어 있다는 말은 적용할 법이 없다는 뜻일 수도 있고, 시스템이 질문을 잘못 만들었다는 뜻일 수도 있다. 전자는 법적 결론에 가까운 말이고, 후자는 스키마의 결함이다. 둘을 구분하지 않으면 시스템의 침묵을 현실의 부재로 오해하게 된다.
이 사례는 앞의 관계 계층과도 조금 다르다. 법인 주택 취득 규칙은 관계 간선이 전혀 없어도 시작할 수 있다. 즉 그래프에서 경로를 잘 찾는 것만으로는 충분하지 않다. 양수인이라는 노드가 법적으로 어떤 당사자 유형인지도, 규칙이 읽을 수 있는 의미로 연결되어야 했다.
Person은 자연인이라는 뜻이 아니었다
여기서 이름이 약간 문제를 만든다. 서비스 모델의 클래스 이름은 Person이다. 그러나 이 이름이 자연인만 뜻하면 안 된다. 자산을 보유하고 거래의 양도인이나 양수인이 되는 주체를 담는 컨테이너에 가깝다.
그래서 Person에 기본값이 자연인인 type 필드를 추가했다.
Person
person_id
display_name
type: NATURAL_PERSON | LEGAL_PERSON
중요한 것은 type이 화면에서 사람을 분류하기 위한 라벨이 아니라는 점이다. 이것은 어떤 법률 요건을 조회할 수 있는지를 바꾸는 입력값이다.
다만 LEGAL_PERSON을 회사 등기 유무와 같은 좁은 의미로 두면 다시 틀릴 수 있다. 이 사례의 지방세법 문언은 법인으로 보는 단체와 법인 아닌 사단·재단 등 개인이 아닌 자까지 함께 포함한다.1 온톨로지의 LegalPerson도 그 넓이를 따라갔다.
PartyType
├── NaturalPerson
└── LegalPerson
└── 법인으로 보는 단체·법인 아닌 사단·재단 등을 포함
현실의 조직 형태를 하나의 단순한 Boolean으로 줄이는 일이 아니라, 조문이 구분하는 경계를 서비스 모델에서 잃지 않는 일이다. 이름이 Person이라도 법률이 보는 주체의 범위는 자연인보다 넓을 수 있다.
TBox에는 질문의 형태를, ABox에는 답의 재료를 넣는다
온톨로지에서 TBox는 사용할 수 있는 개념과 연결의 문법을 정한다. "당사자 유형이라는 개념이 있고, 어떤 요건은 특정 당사자 유형을 요구할 수 있다"는 문법이 TBox다. ABox는 그 문법으로 만든 실제 지식이다. "이 법인 주택 취득 요건은 LegalPerson을 요구한다"는 연결이 ABox다.
이번에는 TBox에 PartyType과 EligibilityCondition.requiresPartyType을 추가했다. 이어서 ABox에 법인 주택 취득 요건과 legalperson을 연결했다. 텍스트 설명에 "법인 취득"이라고 써 두는 것만으로는 Resolver가 읽을 수 없기 때문이다.
이번에 만든 연결은 다음과 같다.
TaxLaw
└── Provision
└── TaxRule
└── hasCondition → EligibilityCondition
└── requiresPartyType → LegalPerson
실제 인스턴스에서는 condition_corporatehousingacquisition이 requiresPartyType으로 legalperson을 가리킨다. 그리고 법인 주택 취득 중과 규칙은 그 조건을 hasCondition으로 참조한다.
같은 문제를 서로 다른 층에서 맡는다.
| 층 | 책임 |
|---|---|
PartyType | 자연인·법인처럼 재사용 가능한 법적 분류의 어휘를 정의한다. |
EligibilityCondition | 어떤 법적 분류가 이 규칙의 요건인지 선언한다. |
TaxRule | 그 요건, 세율, 예외, 시행 시점을 하나의 규칙 단위로 묶는다. |
Person.type | 실제 가구 상태에서 취득자가 어떤 분류에 속하는지 기록한다. |
SemanticMapper | 서비스의 enum을 온톨로지의 naturalperson·legalperson 개체로 번역한다. |
여기서 NaturalPerson과 LegalPerson은 온톨로지의 새로운 하위 클래스가 아니라 PartyType의 통제어휘 개체다. 규칙은 PartyType이라는 공통 형식을 알고, 각 조건은 필요한 개체를 가리킨다. 덕분에 새 규칙이 생겨도 "법인"이라는 같은 법적 분류를 재사용할 수 있다. TBox에 법인 관련 조문마다 새 클래스를 계속 늘리는 것보다, 어휘와 규칙을 분리하는 편이 낫다.
그리고 Customer 데이터 모델은 온톨로지 IRI를 직접 알지 않는다. Person.type = LEGAL_PERSON이라는 서비스 내부 사실은 SemanticMapper에서만 legalperson으로 번역된다. 가족의 사실 데이터와 공용 세법 지식 사이에 매핑 계층을 둔 이유다. 한쪽의 명명 방식이나 저장 방식이 바뀌어도 다른 쪽을 바로 오염시키지 않으려는 경계다.
Resolver도 두 갈래가 되어야 했다
TBox에 속성을 하나 더 넣는다고 실행 경로가 저절로 달라지지는 않는다. 기존 Resolver는 "관계가 있으면 관계 요건 규칙을 찾는다"는 한 가지 길만 갖고 있었다. 이 길은 남겨 두되, 관계가 없다는 이유로 전체 탐색을 끝내지 않게 바꿨다.
Stage 1a: Relationship path
실제 관계 → 세법상 관계 해석 → requiresRelationship 규칙 후보
Stage 1b: Party-type path
취득자 Person.type → PartyType 매핑 → requiresPartyType 규칙 후보
Stage 2: Applicability evaluation
후보별 사건 패턴·시간·예외·추가 사실을 평가
두 경로는 경쟁하지 않는다. 후보를 넓게 모으는 서로 다른 진입점이다. 가족 간 저가 양도처럼 관계 경로가 필요한 사건도 있고, 법인 주택 취득처럼 당사자 유형 경로가 필요한 사건도 있다. 앞으로는 자산 유형이나 지역처럼 또 다른 요건 경로가 생길 수도 있다.
중요한 것은 Stage 1의 결과가 여전히 CANDIDATE라는 점이다. 당사자 유형이 맞는다고 세무 결과가 끝나는 것은 아니다. 이 규칙에도 거래 원인과 주택 해당성, 시점, 예외처럼 더 확인해야 할 사실이 있다. 현재의 수직 슬라이스는 당사자 유형으로 이 규칙을 놓치지 않고 찾는 데까지를 책임진다. 세액을 확정하는 서비스라고 말하지 않는다.
2편에서 나눈 Candidate와 Applicable의 구분은 여기서도 같은 역할을 한다. "법인이라는 사실 때문에 이 규칙을 검토해야 한다"와 "이 거래의 모든 법정 요건과 예외를 검토한 뒤 세액이 확정됐다"는 전혀 다른 문장이다.
테스트가 고친 것은 코드보다 질문이었다
새 테스트는 단순하다.
등록된 당사자 관계 없음
주택 매매 거래 있음
취득자 = LEGAL_PERSON
→ 법인 주택 취득 중과 규칙을 찾는다
취득자 = NATURAL_PERSON
→ 그 규칙을 찾지 않는다
이 대조가 필요한 이유는 긍정 사례 하나만으로는 이전의 조기 종료 버그가 다시 생겼을 때 알아차리기 어렵기 때문이다. 법인은 매칭되고 자연인은 매칭되지 않아야 한다. 테스트는 단지 새 분기를 통과시키는 장치가 아니라, 이 규칙의 질문이 "두 사람이 어떤 관계인가"가 아니라 "취득자가 어떤 법적 성격인가"라는 사실을 코드에 고정한다.
두 신규 테스트는 통과했다. 이 변화에서 중요한 것은 테스트 개수보다 대조의 모양이다. 관계가 없을 때도 법인은 후보를 얻고 자연인은 얻지 않는다. Resolver가 "누구와 어떤 관계인가"만 묻던 상태에서 "이 주체는 법이 무엇으로 보는가"까지 묻게 됐다는 회귀 방지 장치다.
스키마는 빈칸을 드러내는 장치다
이번 작업에서는 또 하나의 빈칸이 보였다. 지방세법 제13조의2 계열 인스턴스 다섯 개의 source_refs가 실제 조문과 무관한 관보 파일을 가리키고 있었다. 규칙 내용은 별도의 법령 원문 대조로 확인됐지만, 인스턴스 안의 출처 링크는 그 사실을 따라가지 못한 상태였다.
이것은 이번 변경 범위에서 고치지 않았다. 대신 후속 과제로 남겼다.
처음에는 출처 필드가 부수적인 메타데이터처럼 보였다. 하지만 온톨로지에서 규칙은 "무엇을 적용할까"만 답하면 충분하지 않다. "왜 이 규칙을 믿을 수 있는가", "어느 시점의 어떤 원문을 근거로 했는가"도 나중에 다시 따라갈 수 있어야 한다. 규칙의 의미와 근거의 연결이 끊기면, 그 규칙은 검색되더라도 검증될 수 없다.
Person.type이 없었던 문제와 source_refs가 잘못 연결된 문제는 성격이 다르다. 하나는 법률 요건을 표현할 수 없는 스키마의 결손이고, 다른 하나는 표현한 규칙의 근거를 추적할 수 없는 출처의 결손이다. 하지만 둘 다 시스템이 모르는 것을 모른다고 말하게 만드는 문제다.
온톨로지를 만드는 일은 현실을 완벽히 복제하는 일이 아니다. 어떤 차이는 지금 모델에 넣고, 어떤 차이는 아직 없다고 표시하며, 나중에 검증할 수 있게 연결을 남기는 일이다.
2편에서 법은 사건 그래프에 가까웠다. 이번에는 그 그래프의 노드에도 법적 타입이 필요하다는 것을 배웠다. 관계와 사건과 시간만으로 충분하지 않다. 누가 무엇을 했는지뿐 아니라, 법이 그 "누구"를 어떤 주체로 세는지도 모델 안에 있어야 한다.
그래프가 답하는 질문은 “무엇과 무엇이 연결되어 있는가”에 가깝다. 온톨로지가 답하는 질문은 “그 연결과 주체가 법에서 무엇을 뜻하는가”에 가깝다. 법인이라는 타입 하나를 넣으며, 둘의 경계가 조금 더 선명해졌다.
참고와 범위
이 글의 거래와 인물은 모델링 설명을 위한 가상 사례다. 법인의 주택 취득과 관련한 실제 판단은 취득 원인, 주택 해당성, 취득 시점, 적용 제외 및 예외, 당시 시행 법령 등 개별 사실관계에 따라 달라진다. 이 글은 세무 자문이 아니라, 그런 사실을 빠뜨리지 않고 다루기 위한 스키마와 추론 경로를 만드는 기록이다.
다음 편 예고
지금까지 Resolver는 거래가 일어났을 때 무엇을 적용할지에 집중했다. 다음에는 질문을 자산 쪽으로 돌린다. 이 부동산을 계속 보유하거나 처분하면 어떤 세금의 층위가 이어지는가. 첫 번째 자산 슬라이스는 부동산 양도소득세다.
Footnotes
-
국가법령정보센터, 지방세법 제13조의2 (2026-08-29 확인). ↩ ↩2