fact_db 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +2 -0
  3. data/.yardopts +5 -0
  4. data/CHANGELOG.md +64 -0
  5. data/README.md +107 -6
  6. data/Rakefile +243 -10
  7. data/db/migrate/001_enable_extensions.rb +1 -0
  8. data/db/migrate/002_create_sources.rb +49 -0
  9. data/db/migrate/003_create_entities.rb +27 -15
  10. data/db/migrate/004_create_entity_aliases.rb +20 -7
  11. data/db/migrate/005_create_facts.rb +37 -21
  12. data/db/migrate/006_create_entity_mentions.rb +14 -6
  13. data/db/migrate/007_create_fact_sources.rb +16 -8
  14. data/docs/api/extractors/index.md +5 -5
  15. data/docs/api/extractors/llm.md +17 -17
  16. data/docs/api/extractors/rule-based.md +14 -14
  17. data/docs/api/facts.md +20 -20
  18. data/docs/api/index.md +4 -4
  19. data/docs/api/models/entity.md +21 -21
  20. data/docs/api/models/fact.md +15 -15
  21. data/docs/api/models/index.md +7 -7
  22. data/docs/api/models/{content.md → source.md} +29 -29
  23. data/docs/api/pipeline/extraction.md +25 -25
  24. data/docs/api/pipeline/index.md +1 -1
  25. data/docs/api/pipeline/resolution.md +4 -4
  26. data/docs/api/services/entity-service.md +20 -20
  27. data/docs/api/services/fact-service.md +12 -12
  28. data/docs/api/services/index.md +5 -5
  29. data/docs/api/services/{content-service.md → source-service.md} +27 -27
  30. data/docs/architecture/database-schema.md +46 -46
  31. data/docs/architecture/entity-resolution.md +6 -6
  32. data/docs/architecture/index.md +10 -10
  33. data/docs/architecture/temporal-facts.md +5 -5
  34. data/docs/architecture/three-layer-model.md +17 -17
  35. data/docs/concepts.md +6 -6
  36. data/docs/examples/basic-usage.md +20 -20
  37. data/docs/examples/hr-onboarding.md +17 -17
  38. data/docs/examples/index.md +4 -4
  39. data/docs/examples/news-analysis.md +23 -23
  40. data/docs/getting-started/database-setup.md +28 -20
  41. data/docs/getting-started/index.md +3 -3
  42. data/docs/getting-started/quick-start.md +33 -30
  43. data/docs/guides/batch-processing.md +26 -26
  44. data/docs/guides/configuration.md +158 -77
  45. data/docs/guides/entity-management.md +14 -14
  46. data/docs/guides/extracting-facts.md +28 -28
  47. data/docs/guides/ingesting-content.md +14 -14
  48. data/docs/guides/llm-integration.md +40 -32
  49. data/docs/guides/temporal-queries.md +11 -11
  50. data/docs/index.md +6 -2
  51. data/examples/.envrc +4 -0
  52. data/examples/.gitignore +1 -0
  53. data/examples/001_configuration.rb +312 -0
  54. data/examples/{basic_usage.rb → 010_basic_usage.rb} +47 -56
  55. data/examples/{entity_management.rb → 020_entity_management.rb} +57 -72
  56. data/examples/{temporal_queries.rb → 030_temporal_queries.rb} +39 -59
  57. data/examples/040_output_formats.rb +177 -0
  58. data/examples/{rule_based_extraction.rb → 050_rule_based_extraction.rb} +39 -45
  59. data/examples/060_fluent_temporal_api.rb +217 -0
  60. data/examples/070_introspection.rb +252 -0
  61. data/examples/{hr_system.rb → 080_hr_system.rb} +56 -75
  62. data/examples/090_ingest_demo.rb +515 -0
  63. data/examples/100_query_context.rb +668 -0
  64. data/examples/110_prove_it.rb +204 -0
  65. data/examples/120_dump_database.rb +358 -0
  66. data/examples/130_rag_feedback_loop.rb +858 -0
  67. data/examples/README.md +229 -15
  68. data/examples/data/lincoln_associates.md +201 -0
  69. data/examples/data/lincoln_biography.md +66 -0
  70. data/examples/data/lincoln_cabinet.md +243 -0
  71. data/examples/data/lincoln_family.md +163 -0
  72. data/examples/data/lincoln_military.md +241 -0
  73. data/examples/data/lincoln_todd_family.md +136 -0
  74. data/examples/ingest_reporter.rb +335 -0
  75. data/examples/utilities.rb +182 -0
  76. data/lib/fact_db/config/defaults.yml +254 -0
  77. data/lib/fact_db/config.rb +94 -35
  78. data/lib/fact_db/database.rb +98 -8
  79. data/lib/fact_db/extractors/base.rb +106 -21
  80. data/lib/fact_db/extractors/llm_extractor.rb +35 -63
  81. data/lib/fact_db/extractors/manual_extractor.rb +46 -6
  82. data/lib/fact_db/extractors/rule_based_extractor.rb +136 -25
  83. data/lib/fact_db/llm/adapter.rb +3 -3
  84. data/lib/fact_db/models/entity.rb +94 -22
  85. data/lib/fact_db/models/entity_alias.rb +41 -7
  86. data/lib/fact_db/models/entity_mention.rb +34 -1
  87. data/lib/fact_db/models/fact.rb +259 -28
  88. data/lib/fact_db/models/fact_source.rb +43 -9
  89. data/lib/fact_db/models/source.rb +113 -0
  90. data/lib/fact_db/pipeline/extraction_pipeline.rb +35 -35
  91. data/lib/fact_db/pipeline/resolution_pipeline.rb +5 -5
  92. data/lib/fact_db/query_result.rb +202 -0
  93. data/lib/fact_db/resolution/entity_resolver.rb +139 -39
  94. data/lib/fact_db/resolution/fact_resolver.rb +86 -14
  95. data/lib/fact_db/services/entity_service.rb +246 -37
  96. data/lib/fact_db/services/fact_service.rb +254 -17
  97. data/lib/fact_db/services/source_service.rb +164 -0
  98. data/lib/fact_db/temporal/query.rb +71 -7
  99. data/lib/fact_db/temporal/query_builder.rb +69 -0
  100. data/lib/fact_db/temporal/timeline.rb +102 -11
  101. data/lib/fact_db/transformers/base.rb +77 -0
  102. data/lib/fact_db/transformers/cypher_transformer.rb +185 -0
  103. data/lib/fact_db/transformers/json_transformer.rb +17 -0
  104. data/lib/fact_db/transformers/raw_transformer.rb +35 -0
  105. data/lib/fact_db/transformers/text_transformer.rb +114 -0
  106. data/lib/fact_db/transformers/triple_transformer.rb +138 -0
  107. data/lib/fact_db/validation/alias_filter.rb +185 -0
  108. data/lib/fact_db/version.rb +1 -1
  109. data/lib/fact_db.rb +281 -30
  110. data/mkdocs.yml +2 -2
  111. metadata +60 -16
  112. data/db/migrate/002_create_contents.rb +0 -44
  113. data/lib/fact_db/models/content.rb +0 -62
  114. data/lib/fact_db/services/content_service.rb +0 -93
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Output Formats Example for FactDb
5
+ #
6
+ # This example demonstrates:
7
+ # - Different output formats for LLM consumption
8
+ # - JSON, Triples, Cypher, and Text transformers
9
+ # - How each format represents the same data differently
10
+
11
+ require_relative "utilities"
12
+
13
+ demo_setup!("FactDb Output Formats Demo")
14
+ demo_configure_logging(__FILE__)
15
+
16
+ facts = FactDb.new
17
+ entity_service = facts.entity_service
18
+ fact_service = facts.fact_service
19
+
20
+ demo_section("Setup: Creating Sample Data")
21
+
22
+ # Create entities
23
+ paula = entity_service.resolve_or_create(
24
+ "Paula Chen",
25
+ kind: :person,
26
+ aliases: ["P. Chen"],
27
+ description: "Principal Engineer"
28
+ )
29
+
30
+ microsoft = entity_service.resolve_or_create(
31
+ "Microsoft",
32
+ kind: :organization,
33
+ aliases: ["MSFT"],
34
+ description: "Technology company"
35
+ )
36
+
37
+ seattle = entity_service.resolve_or_create(
38
+ "Seattle",
39
+ kind: :place,
40
+ description: "City in Washington state"
41
+ )
42
+
43
+ puts "Created entities: #{paula.name}, #{microsoft.name}, #{seattle.name}"
44
+
45
+ # Create facts
46
+ fact_service.create(
47
+ "Paula Chen is Principal Engineer at Microsoft",
48
+ valid_at: Date.new(2024, 1, 10),
49
+ extraction_method: :manual,
50
+ confidence: 1.0,
51
+ mentions: [
52
+ { entity_id: paula.id, role: :subject, text: "Paula Chen" },
53
+ { entity_id: microsoft.id, role: :object, text: "Microsoft" }
54
+ ]
55
+ )
56
+
57
+ fact_service.create(
58
+ "Paula Chen works at the Seattle office",
59
+ valid_at: Date.new(2024, 1, 10),
60
+ extraction_method: :manual,
61
+ confidence: 0.95,
62
+ mentions: [
63
+ { entity_id: paula.id, role: :subject, text: "Paula Chen" },
64
+ { entity_id: seattle.id, role: :location, text: "Seattle" }
65
+ ]
66
+ )
67
+
68
+ fact_service.create(
69
+ "Paula Chen reports to Sarah Kim",
70
+ valid_at: Date.new(2024, 1, 10),
71
+ extraction_method: :manual,
72
+ confidence: 0.9,
73
+ mentions: [
74
+ { entity_id: paula.id, role: :subject, text: "Paula Chen" }
75
+ ]
76
+ )
77
+
78
+ puts "Created facts about Paula Chen"
79
+
80
+ demo_section("Section 1: JSON Format (Default)")
81
+
82
+ json_results = facts.query_facts(entity: paula.id, format: :json)
83
+ puts "\nJSON output:"
84
+ puts JSON.pretty_generate(json_results.to_h)
85
+
86
+ demo_section("Section 2: Triples Format (Subject-Predicate-Object)")
87
+
88
+ triples_results = facts.query_facts(entity: paula.id, format: :triples)
89
+ puts "\nTriples output:"
90
+ triples_results.each do |triple|
91
+ puts " #{triple.inspect}"
92
+ end
93
+
94
+ puts <<~EXPLANATION
95
+
96
+ Triples format is ideal for:
97
+ - Knowledge graph representations
98
+ - Semantic reasoning
99
+ - LLM structured understanding
100
+ EXPLANATION
101
+
102
+ demo_section("Section 3: Cypher Format (Graph Notation)")
103
+
104
+ cypher_results = facts.query_facts(entity: paula.id, format: :cypher)
105
+ puts "\nCypher output:"
106
+ puts cypher_results
107
+
108
+ puts <<~EXPLANATION
109
+
110
+ Cypher format is ideal for:
111
+ - Graph database imports (Neo4j compatible)
112
+ - Visualizing entity relationships
113
+ - Understanding connection patterns
114
+ EXPLANATION
115
+
116
+ demo_section("Section 4: Text Format (Human-Readable)")
117
+
118
+ text_results = facts.query_facts(entity: paula.id, format: :text)
119
+ puts "\nText output:"
120
+ puts text_results
121
+
122
+ puts <<~EXPLANATION
123
+
124
+ Text format is ideal for:
125
+ - Direct LLM consumption
126
+ - Human debugging
127
+ - Report generation
128
+ EXPLANATION
129
+
130
+ demo_section("Section 5: Raw Format (ActiveRecord Objects)")
131
+
132
+ raw_results = facts.query_facts(entity: paula.id, format: :raw)
133
+ puts "\nRaw output (first result):"
134
+ if raw_results.respond_to?(:raw_facts) && raw_results.raw_facts.any?
135
+ fact = raw_results.raw_facts.first
136
+ puts " Class: #{fact.class}"
137
+ puts " ID: #{fact.id}"
138
+ puts " Text: #{fact.text}"
139
+ puts " Valid at: #{fact.valid_at}"
140
+ else
141
+ puts " Results: #{raw_results.inspect}"
142
+ end
143
+
144
+ demo_section("Section 6: Format Comparison - Same Query, Different Views")
145
+
146
+ puts "\nQuerying current facts for Paula Chen in all formats:\n"
147
+
148
+ formats = %i[json triples cypher text]
149
+
150
+ formats.each do |format|
151
+ puts "\n--- #{format.upcase} ---"
152
+ result = facts.current_facts_for(paula.id, format: format)
153
+
154
+ case format
155
+ when :json
156
+ if result.respond_to?(:to_h)
157
+ puts "#{result.fact_count} facts, #{result.entity_count} entities"
158
+ else
159
+ puts "#{result.size} facts" if result.respond_to?(:size)
160
+ end
161
+ when :triples
162
+ puts "#{result.size} triples generated"
163
+ when :cypher
164
+ puts "#{result.lines.count} lines of Cypher notation"
165
+ when :text
166
+ puts result.lines.first(5).join
167
+ puts "..." if result.lines.count > 5
168
+ end
169
+ end
170
+
171
+ demo_section("Section 7: Temporal Queries with Formats")
172
+
173
+ puts "\nFacts at specific date (2024-06-01) in Cypher format:"
174
+ temporal_cypher = facts.facts_at(Date.new(2024, 6, 1), entity: paula.id, format: :cypher)
175
+ puts temporal_cypher
176
+
177
+ demo_footer
@@ -9,28 +9,22 @@
9
9
  # - Processing extracted facts into the database
10
10
  # - Handling extraction results
11
11
 
12
- require "bundler/setup"
13
- require "fact_db"
12
+ require_relative "utilities"
13
+
14
+ demo_setup!("FactDb Rule-Based Extraction Demo")
15
+ demo_configure_logging(__FILE__)
14
16
 
15
17
  FactDb.configure do |config|
16
- config.database_url = ENV.fetch("DATABASE_URL", "postgres://#{ENV['USER']}@localhost/fact_db_demo")
17
18
  config.default_extractor = :rule_based
18
19
  end
19
20
 
20
- # Ensure database tables exist
21
- FactDb::Database.migrate!
22
-
23
- clock = FactDb.new
24
-
25
- puts "=" * 60
26
- puts "FactDb Rule-Based Extraction Demo"
27
- puts "=" * 60
21
+ facts = FactDb.new
28
22
 
29
23
  # Sample documents to process
30
24
  documents = [
31
25
  {
32
26
  title: "Company Announcement",
33
- type: :document,
27
+ kind: :document,
34
28
  text: <<~TEXT
35
29
  FOR IMMEDIATE RELEASE - January 15, 2026
36
30
 
@@ -51,7 +45,7 @@ documents = [
51
45
  },
52
46
  {
53
47
  title: "HR Update Email",
54
- type: :email,
48
+ kind: :email,
55
49
  text: <<~TEXT
56
50
  From: hr@example.com
57
51
  Subject: Team Updates - Q1 2026
@@ -77,7 +71,7 @@ documents = [
77
71
  },
78
72
  {
79
73
  title: "Meeting Notes",
80
- type: :meeting_notes,
74
+ kind: :meeting_notes,
81
75
  text: <<~TEXT
82
76
  Project Status Meeting - January 22, 2026
83
77
 
@@ -99,8 +93,10 @@ documents = [
99
93
  # Create the rule-based extractor
100
94
  extractor = FactDb::Extractors::Base.for(:rule_based)
101
95
 
102
- # Section 1: Process Each Document
103
- puts "\n--- Section 1: Processing Documents ---\n"
96
+ # Store ingested content for later use
97
+ ingested_content = {}
98
+
99
+ demo_section("Section 1: Process Each Document")
104
100
 
105
101
  documents.each_with_index do |doc, index|
106
102
  puts "\n#{'=' * 40}"
@@ -108,12 +104,13 @@ documents.each_with_index do |doc, index|
108
104
  puts "=" * 40
109
105
 
110
106
  # Ingest the content
111
- content = clock.ingest(
107
+ content = facts.ingest(
112
108
  doc[:text],
113
- type: doc[:type],
109
+ kind: doc[:kind],
114
110
  title: doc[:title],
115
111
  captured_at: Time.now
116
112
  )
113
+ ingested_content[index] = content
117
114
  puts "Ingested content ID: #{content.id}"
118
115
 
119
116
  # Extract facts and entities
@@ -136,23 +133,25 @@ documents.each_with_index do |doc, index|
136
133
  if entities.any?
137
134
  puts "\nExtracted #{entities.length} entities:"
138
135
  entities.each do |entity|
139
- puts " - #{entity[:name]} (#{entity[:type]})"
136
+ puts " - #{entity[:name]} (#{entity[:kind]})"
140
137
  end
141
138
  end
142
139
  end
143
140
 
144
- # Section 2: Save Extracted Facts to Database
145
- puts "\n\n--- Section 2: Saving Extracted Facts ---\n"
141
+ demo_section("Section 2: Saving Extracted Facts")
146
142
 
147
- entity_service = clock.entity_service
148
- fact_service = clock.fact_service
143
+ entity_service = facts.entity_service
144
+ fact_service = facts.fact_service
149
145
 
150
- # Process first document in detail
151
- sample_doc = documents.first
152
- content = clock.content_service.search(sample_doc[:title]).first
146
+ # Process the third document (Meeting Notes) which has extractable facts
147
+ sample_doc = documents[2] # "Meeting Notes" extracts 2 facts
148
+ content = ingested_content[2]
153
149
  context = { source_id: content.id, captured_at: content.captured_at }
154
150
  result = extractor.extract(sample_doc[:text], context)
155
151
 
152
+ puts "Processing: #{sample_doc[:title]}"
153
+ puts "Found #{result.length} facts to save"
154
+
156
155
  result.each do |fact_data|
157
156
  # Resolve or create mentioned entities
158
157
  mention_records = []
@@ -161,7 +160,7 @@ result.each do |fact_data|
161
160
  # Try to resolve the entity, create if not found
162
161
  entity = entity_service.resolve_or_create(
163
162
  mention[:name],
164
- type: mention[:type] || :unknown,
163
+ kind: mention[:kind] || :unknown,
165
164
  description: "Auto-extracted entity"
166
165
  )
167
166
 
@@ -184,30 +183,28 @@ result.each do |fact_data|
184
183
  )
185
184
 
186
185
  # Link to source content
187
- fact.add_source(content: content, type: :primary, confidence: fact_data[:confidence])
186
+ fact.add_source(source: content, kind: :primary, confidence: fact_data[:confidence])
188
187
 
189
- puts "Saved fact: #{fact.fact_text}"
188
+ puts "Saved fact: #{fact.text}"
190
189
  puts " ID: #{fact.id}, Mentions: #{fact.entity_mentions.count}"
191
190
  end
192
191
 
193
- # Section 3: Query the Extracted Data
194
- puts "\n--- Section 3: Querying Extracted Data ---\n"
192
+ demo_section("Section 3: Query the Extracted Data")
195
193
 
196
194
  # Find all extracted entities
197
195
  puts "\nAll extracted entities:"
198
- FactDb::Models::Entity.where(resolution_status: :resolved).order(:canonical_name).each do |entity|
196
+ FactDb::Models::Entity.where(resolution_status: :resolved).order(:name).each do |entity|
199
197
  fact_count = entity.facts.count
200
- puts " #{entity.canonical_name} (#{entity.entity_type}) - #{fact_count} facts"
198
+ puts " #{entity.name} (#{entity.kind}) - #{fact_count} facts"
201
199
  end
202
200
 
203
201
  # Find facts by extraction method
204
202
  puts "\nFacts extracted by rule-based extractor:"
205
203
  FactDb::Models::Fact.by_extraction_method(:rule_based).limit(10).each do |fact|
206
- puts " [#{fact.confidence}] #{fact.fact_text}"
204
+ puts " [#{fact.confidence}] #{fact.text}"
207
205
  end
208
206
 
209
- # Section 4: Pattern Examples
210
- puts "\n--- Section 4: Extraction Pattern Examples ---\n"
207
+ demo_section("Section 4: Pattern Examples")
211
208
 
212
209
  test_patterns = [
213
210
  "John Smith works at Acme Corp as a Senior Engineer.",
@@ -235,14 +232,13 @@ test_patterns.each do |pattern|
235
232
  end
236
233
  end
237
234
 
238
- # Section 5: Statistics
239
- puts "\n--- Section 5: Extraction Statistics ---\n"
235
+ demo_section("Section 5: Statistics")
240
236
 
241
- content_stats = clock.content_service.stats
242
- fact_stats = clock.fact_service.stats
243
- entity_stats = entity_service.stats
237
+ source_stats = facts.source_service.stats
238
+ fact_stats = facts.fact_service.stats
239
+ entity_stats = facts.entity_service.stats
244
240
 
245
- puts "Content ingested: #{content_stats[:total]}"
241
+ puts "Content ingested: #{source_stats[:total]}"
246
242
  puts "Entities created: #{entity_stats[:total]}"
247
243
  puts "Facts extracted: #{fact_stats[:total]}"
248
244
 
@@ -253,6 +249,4 @@ if fact_stats[:by_extraction_method]
253
249
  end
254
250
  end
255
251
 
256
- puts "\n" + "=" * 60
257
- puts "Rule-Based Extraction Demo Complete!"
258
- puts "=" * 60
252
+ demo_footer
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Fluent Temporal API Example for FactDb
5
+ #
6
+ # This example demonstrates:
7
+ # - The new fluent query builder API: facts.at(date).query(...)
8
+ # - Comparing facts between two dates with diff()
9
+ # - Point-in-time queries with chaining
10
+ # - Getting entity state at specific moments
11
+
12
+ require_relative "utilities"
13
+
14
+ demo_setup!("FactDb Fluent Temporal API Demo")
15
+ demo_configure_logging(__FILE__)
16
+
17
+ facts = FactDb.new
18
+ entity_service = facts.entity_service
19
+ fact_service = facts.fact_service
20
+
21
+ demo_section("Setup: Creating Career Progression Data")
22
+
23
+ # Create entities
24
+ alex = entity_service.resolve_or_create(
25
+ "Alex Rivera",
26
+ kind: :person,
27
+ description: "Software professional"
28
+ )
29
+
30
+ startup = entity_service.resolve_or_create(
31
+ "TechStartup Inc",
32
+ kind: :organization,
33
+ description: "Early stage company"
34
+ )
35
+
36
+ bigcorp = entity_service.resolve_or_create(
37
+ "BigCorp Systems",
38
+ kind: :organization,
39
+ description: "Enterprise software company"
40
+ )
41
+
42
+ sf = entity_service.resolve_or_create(
43
+ "San Francisco",
44
+ kind: :place
45
+ )
46
+
47
+ nyc = entity_service.resolve_or_create(
48
+ "New York City",
49
+ kind: :place
50
+ )
51
+
52
+ puts "Created entities for Alex's career story"
53
+
54
+ # Create temporal facts showing career progression
55
+ # 2020: Alex joins TechStartup as Junior Developer
56
+ fact_service.find_or_create(
57
+ "Alex Rivera is Junior Developer at TechStartup Inc",
58
+ valid_at: Date.new(2020, 3, 1),
59
+ invalid_at: Date.new(2022, 6, 30),
60
+ extraction_method: :manual,
61
+ mentions: [
62
+ { entity_id: alex.id, role: :subject, text: "Alex Rivera" },
63
+ { entity_id: startup.id, role: :object, text: "TechStartup Inc" }
64
+ ]
65
+ )
66
+
67
+ fact_service.find_or_create(
68
+ "Alex Rivera works at San Francisco office",
69
+ valid_at: Date.new(2020, 3, 1),
70
+ invalid_at: Date.new(2023, 8, 31),
71
+ extraction_method: :manual,
72
+ mentions: [
73
+ { entity_id: alex.id, role: :subject, text: "Alex Rivera" },
74
+ { entity_id: sf.id, role: :location, text: "San Francisco" }
75
+ ]
76
+ )
77
+
78
+ # 2022: Promoted to Senior Developer
79
+ fact_service.find_or_create(
80
+ "Alex Rivera is Senior Developer at TechStartup Inc",
81
+ valid_at: Date.new(2022, 7, 1),
82
+ invalid_at: Date.new(2023, 8, 31),
83
+ extraction_method: :manual,
84
+ mentions: [
85
+ { entity_id: alex.id, role: :subject, text: "Alex Rivera" },
86
+ { entity_id: startup.id, role: :object, text: "TechStartup Inc" }
87
+ ]
88
+ )
89
+
90
+ # 2023: Moves to BigCorp
91
+ fact_service.find_or_create(
92
+ "Alex Rivera is Principal Engineer at BigCorp Systems",
93
+ valid_at: Date.new(2023, 9, 1),
94
+ extraction_method: :manual,
95
+ mentions: [
96
+ { entity_id: alex.id, role: :subject, text: "Alex Rivera" },
97
+ { entity_id: bigcorp.id, role: :object, text: "BigCorp Systems" }
98
+ ]
99
+ )
100
+
101
+ fact_service.find_or_create(
102
+ "Alex Rivera works at New York City office",
103
+ valid_at: Date.new(2023, 9, 1),
104
+ extraction_method: :manual,
105
+ mentions: [
106
+ { entity_id: alex.id, role: :subject, text: "Alex Rivera" },
107
+ { entity_id: nyc.id, role: :location, text: "New York City" }
108
+ ]
109
+ )
110
+
111
+ puts "Created career progression facts (2020-2024)"
112
+
113
+ demo_section("Section 1: Fluent Query Builder - Basic Usage")
114
+
115
+ puts "\nQuery: What was Alex's role in 2021?"
116
+ results_2021 = facts.at("2021-06-15").facts_for(alex.id)
117
+ puts "Date: 2021-06-15"
118
+ results_2021.each_fact do |fact|
119
+ puts " - #{fact[:text]}"
120
+ end
121
+
122
+ puts "\nQuery: What was Alex's role in 2022 (after promotion)?"
123
+ results_2022 = facts.at("2022-09-01").facts_for(alex.id)
124
+ puts "Date: 2022-09-01"
125
+ results_2022.each_fact do |fact|
126
+ puts " - #{fact[:text]}"
127
+ end
128
+
129
+ puts "\nQuery: What is Alex's current situation?"
130
+ results_now = facts.at(Date.today).facts_for(alex.id)
131
+ puts "Date: #{Date.today}"
132
+ results_now.each_fact do |fact|
133
+ puts " - #{fact[:text]}"
134
+ end
135
+
136
+ demo_section("Section 2: Query Builder with Output Formats")
137
+
138
+ puts "\nAlex's 2023 state as Cypher graph:"
139
+ cypher_2023 = facts.at("2023-10-01").facts_for(alex.id, format: :cypher)
140
+ puts cypher_2023
141
+
142
+ puts "\nAlex's 2023 state as Triples:"
143
+ triples_2023 = facts.at("2023-10-01").facts_for(alex.id, format: :triples)
144
+ triples_2023.each { |t| puts " #{t.inspect}" }
145
+
146
+ demo_section("Section 3: Comparing Two Points in Time")
147
+
148
+ puts "\nComparing Alex's situation: 2021-06-01 vs 2024-01-01"
149
+ diff_result = facts.diff(nil, from: "2021-06-01", to: "2024-01-01")
150
+
151
+ puts "\nRemoved facts (no longer true):"
152
+ diff_result[:removed].each do |fact|
153
+ puts " - #{fact.text}"
154
+ end
155
+
156
+ puts "\nAdded facts (became true):"
157
+ diff_result[:added].each do |fact|
158
+ puts " - #{fact.text}"
159
+ end
160
+
161
+ puts "\nUnchanged facts:"
162
+ if diff_result[:unchanged].empty?
163
+ puts " (none - everything changed!)"
164
+ else
165
+ diff_result[:unchanged].each do |fact|
166
+ puts " - #{fact.text}"
167
+ end
168
+ end
169
+
170
+ demo_section("Section 4: Query Builder - Compare To")
171
+
172
+ puts "\nUsing fluent API to compare dates:"
173
+ comparison = facts.at("2020-06-01").compare_to("2023-10-01")
174
+
175
+ puts "\nFrom #{comparison[:from]} to #{comparison[:to]}:"
176
+ puts " Added: #{comparison[:added].count} facts"
177
+ puts " Removed: #{comparison[:removed].count} facts"
178
+ puts " Unchanged: #{comparison[:unchanged].count} facts"
179
+
180
+ demo_section("Section 5: Career Timeline View")
181
+
182
+ checkpoints = [
183
+ Date.new(2020, 6, 1),
184
+ Date.new(2021, 6, 1),
185
+ Date.new(2022, 6, 1),
186
+ Date.new(2022, 8, 1),
187
+ Date.new(2023, 6, 1),
188
+ Date.new(2023, 10, 1),
189
+ Date.new(2024, 1, 1)
190
+ ]
191
+
192
+ puts "\nAlex Rivera's Career Timeline:\n"
193
+ checkpoints.each do |date|
194
+ snapshot = facts.at(date).facts_for(alex.id, format: :text)
195
+ fact_count = facts.at(date).facts_for(alex.id).fact_count
196
+
197
+ puts "#{date.strftime('%Y-%m-%d')} (#{fact_count} facts):"
198
+ if fact_count.zero?
199
+ puts " (no facts yet)"
200
+ else
201
+ facts.at(date).facts_for(alex.id).each_fact do |fact|
202
+ puts " - #{fact[:text]}"
203
+ end
204
+ end
205
+ puts
206
+ end
207
+
208
+ demo_section("Section 6: State For - Entity State at a Date")
209
+
210
+ puts "\nSnapshot of Alex's state on 2022-08-01:"
211
+ state = facts.at("2022-08-01").state_for(alex.id)
212
+ puts "Facts at this moment:"
213
+ state.each_fact do |fact|
214
+ puts " - #{fact[:text]}"
215
+ end
216
+
217
+ demo_footer