02-types-entity

Dec 7, 2025

EntityNode

📁 관련 코드: lib/types/nodes/entity.ts

이메일에서 추출된 **명명된 개체(Named Entity)**를 저장합니다.

핵심 개념: Named Entity란?

"ABC회사의 신제품 X-Pro가 다음 달 CES에서 발표됩니다"

추출된 Entity:
┌─────────────────────────────────────────────────────────┐
 [COMPANY] ABC회사                                       
 [PRODUCT] X-Pro                                        
 [EVENT]   CES                                          
└─────────────────────────────────────────────────────────┘

Entity vs Person


Entity

Person

정의

사물, 조직, 장소 등

사람

식별자

canonicalName

email

예시

ABC회사, 프로젝트X

김철수

왜 분리하는가?

  • Person은 이메일로 고유 식별 가능

  • Entity는 이름만으로 식별 (동음이의어 문제)

EntityType

enum EntityType {
  COMPANY = 'company',    // 회사, 조직
  PRODUCT = 'product',    // 제품, 서비스
  PROJECT = 'project',    // 프로젝트, 이니셔티브
  LOCATION = 'location',  // 장소
  EVENT = 'event',        // 이벤트, 컨퍼런스
  OTHER = 'other',        // 기타
}

분류 기준

타입

키워드/패턴

예시

COMPANY

회사, Inc., Corp., Ltd.

"ABC Inc.", "네이버"

PRODUCT

제품, 서비스, 앱, 플랫폼

"iPhone", "Slack"

PROJECT

프로젝트, TF, 이니셔티브

"프로젝트 알파", "2024 리뉴얼"

LOCATION

도시, 국가, 빌딩, 회의실

"서울", "회의실 A"

EVENT

컨퍼런스, 세미나, 미팅

"CES 2024", "개발자 데이"

Canonical Name: 동일 엔티티 매칭

문제 상황

이메일 1: "ABC 회사와 미팅했습니다"
이메일 2: "abc회사 담당자가..."
이메일 3: "ABC Inc.에서 연락왔어요"

모두 같은 회사인데, 문자열이 다름

해결: canonicalizeName 함수

canonicalizeName("ABC 회사")   // → "abc 회사"
canonicalizeName("abc회사")    // → "abc회사"
canonicalizeName("ABC Inc.")   // → "abc inc"

정규화 규칙:

  1. 소문자 변환

  2. 앞뒤 공백 제거

  3. 특수문자 제거 (알파벳, 숫자, 한글, 공백만 유지)

  4. 연속 공백 단일화

한계와 보완

"삼성""삼성전자"같은가?
"MS""Microsoft"같은가

aliases 배열로 해결

const samsung: EntityNode = {
  name: "삼성전자",
  canonicalName: "삼성전자",
  aliases: ["삼성", "Samsung", "SEC"],
  // ...
};

Aliases: 이름 변형 처리

aliases: string[];  // ["삼성", "Samsung", "SEC"]

사용 시나리오

시나리오 1: 약어 처리

"MS에서 새 기능 발표" aliases에 "MS" 추가
"Microsoft Teams 업데이트" 기존 EntityNode에 연결

시나리오 2: 오타 처리

"구글" vs "구굴" LLM이 오타 인식 aliases에 추가

시나리오 3: 다국어

"Google" = "구글" aliases로 연결

mightBeSameEntity: 중복 감지

function mightBeSameEntity(a: EntityNode, b: EntityNode):

검사 로직

1. canonicalName 일치? true
2. a.name이 b.aliases에 있음? true
3. b.name이 a.aliases에 있음? true
4. 정규화된 aliases 교집합 있음? true
5. 모두 아니면 false

사용 예시

const entity1 = createEntityNode({ name: "삼성전자", ... });
const entity2 = createEntityNode({ name: "Samsung", ... });

// 처음엔 별개로 저장

// 나중에 "삼성전자 = Samsung" 정보 발견
entity1.aliases.push("Samsung");

// 이제 중복 감지됨
mightBeSameEntity(entity1, entity2);  // true

// 병합
const merged = mergeEntityNodes(entity1, entity2);

Mention Statistics

mentionCount: number;    // 언급 횟수
firstMentionAt: Date;    // 첫 언급
lastMentionAt: Date;     // 마지막 언급

왜 통계가 필요한가?

시나리오: "자주 언급되는 회사가 뭐야?"

const topCompanies = entities
  .filter(e => e.entityType === EntityType.COMPANY)
  .sort((a, b) => b.mentionCount - a.mentionCount)
  .slice(0, 5);

시나리오: "최근에 논의된 프로젝트"

const recentProjects = entities
  .filter(e => e.entityType === EntityType.PROJECT)
  .sort((a, b) => b.lastMentionAt.getTime() - a.lastMentionAt.getTime());

그래프에서의 관계

[EntityNode: ABC회사]
        
        ├── MENTIONED_IN ←── [MemoryNode: 계약 체결]
        
        ├── MENTIONED_IN ←── [MemoryNode: 미팅 예정]
        
        ├── EMPLOYS ────────→ [PersonNode: 김철수]
        
        └── OWNS ───────────→ [EntityNode: X-Pro 제품]

Entity 간 관계

[COMPANY: ABC회사]
        
        ├── PRODUCES ──→ [PRODUCT: X-Pro]
        
        └── RUNS ──────→ [PROJECT: 2024 리뉴얼]

[EVENT: CES 2024]
        
        └── HELD_AT ───→ [LOCATION: 라스베가스]

Factory 사용 예시

const company = createEntityNode({
  id: generateUUID(),
  name: "ABC회사",
  entityType: EntityType.COMPANY,
  description: "IT 서비스 기업",
  website: "https://abc.com",
  industry: "Technology",
  firstMentionAt: new Date(),
  lastMentionAt: new Date(),
  sourceType: SourceType.BOOTSTRAPPED,
  confidence: 0.9,
});

// canonicalName은 자동 생성: "abc회사"

다음 문서

  • → TaskNode: 할일/요청 스키마

  • → PersonNode: Person과 Entity의 관계