Class: FactDb::Temporal::Timeline

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/fact_db/temporal/timeline.rb

Overview

Builds and analyzes temporal timelines of facts for an entity

Provides methods to view an entity’s history, group events by time periods, find overlapping facts, and compare states at different points in time. Includes Enumerable for easy iteration over timeline events.

Examples:

Build a timeline for an entity

timeline = Timeline.new.build(entity_id: person.id)
timeline.by_year.each { |year, events| puts "#{year}: #{events.count} events" }

Find currently active facts

active_facts = timeline.active

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTimeline

Initializes a new empty Timeline



25
26
27
# File 'lib/fact_db/temporal/timeline.rb', line 25

def initialize
  @events = []
end

Instance Attribute Details

#eventsArray<TimelineEvent> (readonly)

Returns the timeline events.

Returns:



22
23
24
# File 'lib/fact_db/temporal/timeline.rb', line 22

def events
  @events
end

Instance Method Details

#activeArray<TimelineEvent>

Returns currently active (valid) events

Returns:



89
90
91
# File 'lib/fact_db/temporal/timeline.rb', line 89

def active
  to_a.select(&:currently_valid?)
end

#between(from, to) ⇒ Array<TimelineEvent>

Returns events in a specific date range

Parameters:

  • from (Date, Time)

    start of range (inclusive)

  • to (Date, Time)

    end of range (inclusive)

Returns:



82
83
84
# File 'lib/fact_db/temporal/timeline.rb', line 82

def between(from, to)
  to_a.select { |event| event.valid_at >= from && event.valid_at <= to }
end

#build(entity_id:, from: nil, to: nil) ⇒ Timeline

Builds a timeline of facts for an entity

Parameters:

  • entity_id (Integer)

    the entity to build timeline for

  • from (Date, Time, nil) (defaults to: nil)

    start of date range (optional)

  • to (Date, Time, nil) (defaults to: nil)

    end of date range (optional)

Returns:

  • (Timeline)

    self for method chaining



43
44
45
46
47
# File 'lib/fact_db/temporal/timeline.rb', line 43

def build(entity_id:, from: nil, to: nil)
  facts = fetch_facts(entity_id, from, to)
  @events = facts.map { |fact| TimelineEvent.new(fact) }
  self
end

#by_monthHash<String, Array<TimelineEvent>>

Groups events by month

Returns:

  • (Hash<String, Array<TimelineEvent>>)

    events grouped by “YYYY-MM” key



73
74
75
# File 'lib/fact_db/temporal/timeline.rb', line 73

def by_month
  to_a.group_by { |event| event.valid_at.strftime("%Y-%m") }
end

#by_yearHash<Integer, Array<TimelineEvent>>

Groups events by year

Returns:

  • (Hash<Integer, Array<TimelineEvent>>)

    events grouped by year



66
67
68
# File 'lib/fact_db/temporal/timeline.rb', line 66

def by_year
  to_a.group_by { |event| event.valid_at.year }
end

#changes_summaryArray<Hash>

Generates a summary of changes between consecutive events

Returns:

  • (Array<Hash>)

    array of hashes with :from, :to, and :gap_days keys



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/fact_db/temporal/timeline.rb', line 129

def changes_summary
  sorted = to_a

  sorted.each_cons(2).map do |prev_event, next_event|
    {
      from: prev_event,
      to: next_event,
      gap_days: (next_event.valid_at.to_date - (prev_event.invalid_at || prev_event.valid_at).to_date).to_i
    }
  end
end

#each {|Hash| ... } ⇒ Enumerator

Iterates over timeline event hashes

Yields:

  • (Hash)

    each event as a hash

Returns:

  • (Enumerator)

    if no block given



33
34
35
# File 'lib/fact_db/temporal/timeline.rb', line 33

def each(&block)
  to_hash.each(&block)
end

#historicalArray<TimelineEvent>

Returns historical (no longer valid) events

Returns:



96
97
98
# File 'lib/fact_db/temporal/timeline.rb', line 96

def historical
  to_a.reject(&:currently_valid?)
end

#overlappingArray<Array<TimelineEvent, TimelineEvent>>

Finds pairs of overlapping events

Two events overlap if their validity periods intersect.

Returns:



105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/fact_db/temporal/timeline.rb', line 105

def overlapping
  result = []
  sorted = to_a

  sorted.each_with_index do |event, i|
    sorted[(i + 1)..].each do |other|
      result << [event, other] if events_overlap?(event, other)
    end
  end

  result
end

#state_at(date) ⇒ Array<TimelineEvent>

Returns the state (valid events) at a specific point in time

Parameters:

  • date (Date, Time)

    the point in time to query

Returns:



122
123
124
# File 'lib/fact_db/temporal/timeline.rb', line 122

def state_at(date)
  to_a.select { |event| event.valid_at?(date) }
end

#to_aArray<TimelineEvent>

Returns events sorted by valid_at date

Returns:



52
53
54
# File 'lib/fact_db/temporal/timeline.rb', line 52

def to_a
  @events.sort_by(&:valid_at)
end

#to_hashArray<Hash>

Returns events as an array of hashes

Returns:

  • (Array<Hash>)

    events converted to hash format



59
60
61
# File 'lib/fact_db/temporal/timeline.rb', line 59

def to_hash
  to_a.map(&:to_hash)
end