Class: FactDb::Models::Entity

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
lib/fact_db/models/entity.rb

Overview

Represents a named entity in the fact database

Entities are real-world things like people, organizations, places, etc. that can be referenced in facts. Entities support aliases for name variations and can be merged to deduplicate records.

Examples:

Create an entity with aliases

entity = Entity.create!(name: "John Smith", kind: "person", resolution_status: "resolved")
entity.add_alias("J. Smith")

Find entities by kind

people = Entity.by_kind("person").not_merged

Constant Summary collapse

STATUSES =

Returns valid resolution statuses.

Returns:

  • (Array<String>)

    valid resolution statuses

%w[unresolved resolved merged split].freeze
ENTITY_KINDS =

Returns valid entity kinds.

Returns:

  • (Array<String>)

    valid entity kinds

%w[person organization place product event concept other].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.nearest_neighbors(embedding, limit: 10) ⇒ ActiveRecord::Relation

Finds entities by vector similarity using pgvector

Parameters:

  • embedding (Array<Float>)

    the embedding vector to search with

  • limit (Integer) (defaults to: 10)

    maximum number of results

Returns:

  • (ActiveRecord::Relation)

    entities ordered by similarity



149
150
151
152
153
# File 'lib/fact_db/models/entity.rb', line 149

def self.nearest_neighbors(embedding, limit: 10)
  return none unless embedding

  order(Arel.sql("embedding <=> '#{embedding}'")).limit(limit)
end

Instance Method Details

#add_alias(text, kind: nil, confidence: 1.0) ⇒ EntityAlias?

Adds an alias to this entity

Validates the alias before creation using AliasFilter. Returns nil if validation fails.

Parameters:

  • text (String)

    the alias text

  • kind (String, nil) (defaults to: nil)

    alias kind (name, nickname, email, handle, abbreviation, title)

  • confidence (Float) (defaults to: 1.0)

    confidence score (0.0 to 1.0)

Returns:

  • (EntityAlias, nil)

    the created alias or nil if validation failed



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/fact_db/models/entity.rb', line 106

def add_alias(text, kind: nil, confidence: 1.0)
  # Pre-validate before attempting to create
  return nil unless Validation::AliasFilter.valid?(text, name: name)

  aliases.find_or_create_by!(name: text) do |a|
    a.kind = kind
    a.confidence = confidence
  end
rescue ActiveRecord::RecordInvalid
  # Alias validation failed (pronoun, generic term, etc.)
  nil
end

#all_aliasesArray<String>

Returns all alias names as an array of strings

Returns:

  • (Array<String>)

    alias names



93
94
95
# File 'lib/fact_db/models/entity.rb', line 93

def all_aliases
  aliases.pluck(:name)
end

#by_kind(k) ⇒ ActiveRecord::Relation

Returns entities of a specific kind

Parameters:

  • k (String)

    the entity kind

Returns:

  • (ActiveRecord::Relation)


49
# File 'lib/fact_db/models/entity.rb', line 49

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

#canonical_entityEntity

Returns the canonical entity (follows merge chain)

If this entity has been merged, recursively follows the canonical_id chain to find the ultimate canonical entity.

Returns:

  • (Entity)

    the canonical entity or self if not merged



86
87
88
# File 'lib/fact_db/models/entity.rb', line 86

def canonical_entity
  merged? ? canonical&.canonical_entity || canonical : self
end

#current_factsActiveRecord::Relation

Returns currently valid canonical facts mentioning this entity

Returns:

  • (ActiveRecord::Relation)

    currently valid facts



132
133
134
# File 'lib/fact_db/models/entity.rb', line 132

def current_facts
  facts.currently_valid.canonical
end

#facts_at(date) ⇒ ActiveRecord::Relation

Returns facts valid at a specific date

Parameters:

  • date (Date, Time)

    the point in time to query

Returns:

  • (ActiveRecord::Relation)

    facts valid at the given date



140
141
142
# File 'lib/fact_db/models/entity.rb', line 140

def facts_at(date)
  facts.valid_at(date).canonical
end

#matches_name?(query) ⇒ Boolean

Checks if the entity matches a query (by name or alias)

Parameters:

  • query (String)

    the name to match (case-insensitive)

Returns:

  • (Boolean)

    true if name or any alias matches



123
124
125
126
127
# File 'lib/fact_db/models/entity.rb', line 123

def matches_name?(query)
  return true if self.name.downcase == query.downcase

  aliases.exists?(["LOWER(name) = ?", query.downcase])
end

#merged?Boolean

Checks if the entity has been merged into another

Returns:

  • (Boolean)

    true if resolution_status is “merged”



76
77
78
# File 'lib/fact_db/models/entity.rb', line 76

def merged?
  resolution_status == "merged"
end

#not_mergedActiveRecord::Relation

Returns entities that have not been merged

Returns:

  • (ActiveRecord::Relation)


64
# File 'lib/fact_db/models/entity.rb', line 64

scope :not_merged, -> { where.not(resolution_status: "merged") }

#resolvedActiveRecord::Relation

Returns entities with “resolved” status

Returns:

  • (ActiveRecord::Relation)


54
# File 'lib/fact_db/models/entity.rb', line 54

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

#resolved?Boolean

Checks if the entity is resolved

Returns:

  • (Boolean)

    true if resolution_status is “resolved”



69
70
71
# File 'lib/fact_db/models/entity.rb', line 69

def resolved?
  resolution_status == "resolved"
end

#unresolvedActiveRecord::Relation

Returns entities with “unresolved” status

Returns:

  • (ActiveRecord::Relation)


59
# File 'lib/fact_db/models/entity.rb', line 59

scope :unresolved, -> { where(resolution_status: "unresolved") }