Skip to content

Entity Model

Stores resolved identities (people, organizations, places, etc.).

Class: FactDb::Models::Entity

entity = FactDb::Models::Entity.new(
  name: "Paula Chen",
  kind: "person"
)

Attributes

Attribute Type Description
id Integer Primary key
name String Authoritative name
kind String Kind (person, organization, place, etc.)
resolution_status String Status (unresolved, resolved, merged)
canonical_id Integer Points to canonical entity if merged
metadata Hash Additional attributes (JSONB)
embedding Vector Semantic search vector
created_at DateTime Record creation time

Entity Kinds

  • person - Individual people
  • organization - Companies, teams, groups
  • place - Locations
  • product - Products, services
  • event - Named events

Resolution Status

  • unresolved - Entity created but not confirmed
  • resolved - Entity identity confirmed
  • merged - Entity merged into another

Associations

has_many :entity_aliases, dependent: :destroy
has_many :entity_mentions
has_many :facts, through: :entity_mentions
belongs_to :merged_into, class_name: 'Entity', optional: true

Instance Methods

add_alias

def add_alias(text, kind: nil, confidence: 1.0)

Add an alias to the entity.

Example:

entity.add_alias("Paula", kind: "nickname", confidence: 0.95)

merged?

def merged?

Returns true if entity has been merged into another.

canonical

def canonical

Returns the canonical entity (follows merge chain).

Example:

# If entity was merged
canonical = entity.canonical  # Returns the canonical entity

Scopes

by_kind

scope :by_kind, ->(k) { where(kind: k) }

Filter by entity kind.

Entity.by_kind('person')

active

scope :active, -> { where.not(resolution_status: 'merged') }

Exclude merged entities.

Entity.active

resolved

scope :resolved, -> { where(resolution_status: 'resolved') }

Only resolved entities.

search_name

scope :search_name, ->(query) {
  where("name ILIKE ?", "%#{query}%")
}

Search by name.

Entity.search_name("paula")

Usage Examples

Create Entity

entity = Entity.create!(
  name: "Paula Chen",
  kind: "person",
  metadata: {
    department: "Engineering",
    employee_id: "E12345"
  }
)

Add Aliases

entity.add_alias("Paula")
entity.add_alias("P. Chen", kind: "abbreviation")
entity.add_alias("Chen, Paula", kind: "formal")

Check Aliases

entity.entity_aliases.each do |a|
  puts "#{a.name} (#{a.kind})"
end
entity.facts.each do |fact|
  puts "#{fact.valid_at}: #{fact.text}"
end

Find Similar Entities

# By name
similar = Entity.search_name("Microsoft")

# By embedding
similar = Entity
  .where.not(embedding: nil)
  .order(Arel.sql("embedding <=> '#{query_embedding}'"))
  .limit(10)

Merge Entities

# entity2 will be merged into entity1
entity2.update!(
  resolution_status: 'merged',
  canonical_id: entity1.id
)

# Copy aliases
entity2.entity_aliases.each do |a|
  entity1.add_alias(a.name, kind: a.kind)
end