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
@@ -10,28 +10,20 @@
10
10
  # - Creating facts manually
11
11
  # - Querying facts
12
12
 
13
- require "bundler/setup"
14
- require "fact_db"
13
+ require_relative "utilities"
14
+ require "amazing_print"
15
+
16
+ demo_setup!("FactDb Basic Usage Demo")
17
+ demo_configure_logging(__FILE__)
15
18
 
16
- # Configure FactDb
17
19
  FactDb.configure do |config|
18
- config.database_url = ENV.fetch("DATABASE_URL", "postgres://#{ENV['USER']}@localhost/fact_db_demo")
19
20
  config.default_extractor = :manual
20
- config.fuzzy_match_threshold = 0.85
21
21
  end
22
22
 
23
- # Ensure database tables exist
24
- FactDb::Database.migrate!
25
-
26
- # Create a new FactDb instance (the "clock")
27
- clock = FactDb.new
28
-
29
- puts "=" * 60
30
- puts "FactDb Basic Usage Demo"
31
- puts "=" * 60
23
+ # Create a new FactDb instance
24
+ facts = FactDb.new
32
25
 
33
- # Step 1: Ingest some content
34
- puts "\n--- Step 1: Ingesting Content ---\n"
26
+ demo_section("Step 1: Ingesting Content")
35
27
 
36
28
  email_content = <<~EMAIL
37
29
  From: hr@acme.com
@@ -49,9 +41,9 @@ email_content = <<~EMAIL
49
41
  HR Department
50
42
  EMAIL
51
43
 
52
- content = clock.ingest(
44
+ content = facts.ingest(
53
45
  email_content,
54
- type: :email,
46
+ kind: :email,
55
47
  title: "New Hire Announcement - Jane Smith",
56
48
  captured_at: Time.new(2026, 1, 8)
57
49
  )
@@ -60,42 +52,40 @@ puts "Ingested content ID: #{content.id}"
60
52
  puts "Content hash: #{content.content_hash}"
61
53
  puts "Word count: #{content.word_count}"
62
54
 
63
- # Step 2: Create entities
64
- puts "\n--- Step 2: Creating Entities ---\n"
55
+ demo_section("Step 2: Creating/Finding Entities")
65
56
 
66
- entity_service = clock.entity_service
57
+ entity_service = facts.entity_service
67
58
 
68
- jane = entity_service.create(
59
+ jane = entity_service.resolve_or_create(
69
60
  "Jane Smith",
70
- type: :person,
61
+ kind: :person,
71
62
  aliases: ["J. Smith"],
72
63
  description: "Director of Engineering at Acme Corp"
73
64
  )
74
- puts "Created entity: #{jane.canonical_name} (ID: #{jane.id})"
65
+ puts "Entity: #{jane.name} (ID: #{jane.id})"
75
66
 
76
- acme = entity_service.create(
67
+ acme = entity_service.resolve_or_create(
77
68
  "Acme Corp",
78
- type: :organization,
69
+ kind: :organization,
79
70
  aliases: ["Acme", "Acme Corporation"],
80
71
  description: "Technology company"
81
72
  )
82
- puts "Created entity: #{acme.canonical_name} (ID: #{acme.id})"
73
+ puts "Entity: #{acme.name} (ID: #{acme.id})"
83
74
 
84
- techstartup = entity_service.create(
75
+ techstartup = entity_service.resolve_or_create(
85
76
  "TechStartup Inc",
86
- type: :organization,
77
+ kind: :organization,
87
78
  aliases: ["TechStartup"],
88
79
  description: "Technology startup company"
89
80
  )
90
- puts "Created entity: #{techstartup.canonical_name} (ID: #{techstartup.id})"
81
+ puts "Entity: #{techstartup.name} (ID: #{techstartup.id})"
91
82
 
92
- # Step 3: Create facts
93
- puts "\n--- Step 3: Creating Facts ---\n"
83
+ demo_section("Step 3: Creating/Finding Facts")
94
84
 
95
- fact_service = clock.fact_service
85
+ fact_service = facts.fact_service
96
86
 
97
87
  # Fact 1: Jane works at Acme
98
- fact1 = fact_service.create(
88
+ fact1 = fact_service.find_or_create(
99
89
  "Jane Smith is Director of Engineering at Acme Corp",
100
90
  valid_at: Date.new(2026, 1, 8),
101
91
  extraction_method: :manual,
@@ -105,11 +95,11 @@ fact1 = fact_service.create(
105
95
  { entity_id: acme.id, role: :object, text: "Acme Corp" }
106
96
  ]
107
97
  )
108
- puts "Created fact: #{fact1.fact_text}"
98
+ puts "Fact: #{fact1.text}"
109
99
  puts " Valid from: #{fact1.valid_at}"
110
100
 
111
101
  # Fact 2: Jane previously worked at TechStartup (now invalid)
112
- fact2 = fact_service.create(
102
+ fact2 = fact_service.find_or_create(
113
103
  "Jane Smith was VP of Engineering at TechStartup Inc",
114
104
  valid_at: Date.new(2023, 1, 1),
115
105
  invalid_at: Date.new(2026, 1, 7),
@@ -120,45 +110,46 @@ fact2 = fact_service.create(
120
110
  { entity_id: techstartup.id, role: :object, text: "TechStartup Inc" }
121
111
  ]
122
112
  )
123
- puts "Created fact: #{fact2.fact_text}"
113
+ puts "Fact: #{fact2.text}"
124
114
  puts " Valid from: #{fact2.valid_at} to #{fact2.invalid_at}"
125
115
 
126
- # Link facts to source content
127
- fact1.add_source(content: content, type: :primary, confidence: 1.0)
128
- fact2.add_source(content: content, type: :supporting, confidence: 0.8)
116
+ # Link facts to source content (skip if already linked)
117
+ fact1.add_source(source: content, kind: :primary, confidence: 1.0) rescue nil
118
+ fact2.add_source(source: content, kind: :supporting, confidence: 0.8) rescue nil
129
119
 
130
- # Step 4: Query facts
131
- puts "\n--- Step 4: Querying Facts ---\n"
120
+ demo_section("Step 4: Querying Facts")
132
121
 
133
122
  # Get current facts about Jane
134
123
  puts "\nCurrent facts about Jane Smith:"
135
124
  current_facts = fact_service.current_facts(entity: jane.id)
136
125
  current_facts.each do |fact|
137
- puts " - #{fact.fact_text}"
126
+ puts " - #{fact.text}"
138
127
  end
139
128
 
140
129
  # Get facts valid at a specific date (when Jane was at TechStartup)
141
130
  puts "\nFacts about Jane on January 1, 2024:"
142
131
  past_facts = fact_service.facts_at(Date.new(2024, 1, 1), entity: jane.id)
143
132
  past_facts.each do |fact|
144
- puts " - #{fact.fact_text}"
133
+ puts " - #{fact.text}"
145
134
  end
146
135
 
147
136
  # Get all facts (including historical)
148
137
  puts "\nAll facts in the system:"
149
- all_facts = clock.query_facts
150
- all_facts.each do |fact|
151
- status = fact.invalid_at ? "(historical)" : "(current)"
152
- puts " - #{fact.fact_text} #{status}"
138
+ all_facts = facts.query_facts
139
+ all_facts.each_fact do |fact|
140
+ status = fact[:invalid_at] ? "(historical)" : "(current)"
141
+ puts " - #{fact[:text]} #{status}"
153
142
  end
154
143
 
155
- # Step 5: Get statistics
156
- puts "\n--- Step 5: Statistics ---\n"
144
+ demo_section("Step 5: Statistics")
145
+
146
+ puts "\nSource stats:"
147
+ ap facts.source_service.stats
148
+
149
+ puts "\nEntity stats:"
150
+ ap entity_service.stats
157
151
 
158
- puts "Content stats: #{clock.content_service.stats}"
159
- puts "Entity stats: #{entity_service.stats}"
160
- puts "Fact stats: #{fact_service.stats}"
152
+ puts "\nFact stats:"
153
+ ap fact_service.stats
161
154
 
162
- puts "\n" + "=" * 60
163
- puts "Demo complete!"
164
- puts "=" * 60
155
+ demo_footer
@@ -11,69 +11,56 @@
11
11
  # - Searching entities
12
12
  # - Building entity timelines
13
13
 
14
- require "bundler/setup"
15
- require "fact_db"
14
+ require_relative "utilities"
16
15
 
17
- FactDb.configure do |config|
18
- config.database_url = ENV.fetch("DATABASE_URL", "postgres://#{ENV['USER']}@localhost/fact_db_demo")
19
- config.fuzzy_match_threshold = 0.85
20
- config.auto_merge_threshold = 0.95
21
- end
22
-
23
- # Ensure database tables exist
24
- FactDb::Database.migrate!
16
+ demo_setup!("FactDb Entity Management Demo")
17
+ demo_configure_logging(__FILE__)
25
18
 
26
- clock = FactDb.new
27
- entity_service = clock.entity_service
28
- fact_service = clock.fact_service
19
+ facts = FactDb.new
20
+ entity_service = facts.entity_service
21
+ fact_service = facts.fact_service
29
22
 
30
- puts "=" * 60
31
- puts "FactDb Entity Management Demo"
32
- puts "=" * 60
33
-
34
- # Section 1: Creating Entities
35
- puts "\n--- Section 1: Creating Entities ---\n"
23
+ demo_section("Section 1: Creating Entities")
36
24
 
37
25
  # Create a person entity with aliases
38
26
  person = entity_service.create(
39
27
  "Robert Johnson",
40
- type: :person,
28
+ kind: :person,
41
29
  aliases: ["Bob Johnson", "R. Johnson", "Bobby"],
42
30
  attributes: { email: "rjohnson@example.com", department: "Sales" },
43
31
  description: "Senior Sales Representative"
44
32
  )
45
- puts "Created person: #{person.canonical_name}"
46
- puts " Aliases: #{person.aliases.map(&:alias_text).join(', ')}"
47
- puts " Type: #{person.entity_type}"
33
+ puts "Created person: #{person.name}"
34
+ puts " Aliases: #{person.aliases.map(&:name).join(', ')}"
35
+ puts " Type: #{person.kind}"
48
36
 
49
37
  # Create organization entities
50
38
  org1 = entity_service.create(
51
39
  "Global Industries Inc",
52
- type: :organization,
40
+ kind: :organization,
53
41
  aliases: ["Global Industries", "GII"],
54
42
  description: "Fortune 500 manufacturing company"
55
43
  )
56
- puts "\nCreated organization: #{org1.canonical_name}"
44
+ puts "\nCreated organization: #{org1.name}"
57
45
 
58
46
  # Create a location entity
59
47
  location = entity_service.create(
60
48
  "San Francisco",
61
- type: :place,
49
+ kind: :place,
62
50
  aliases: ["SF", "San Fran"],
63
51
  attributes: { country: "USA", state: "California" }
64
52
  )
65
- puts "Created place: #{location.canonical_name}"
53
+ puts "Created place: #{location.name}"
66
54
 
67
- # Section 2: Entity Resolution
68
- puts "\n--- Section 2: Entity Resolution ---\n"
55
+ demo_section("Section 2: Entity Resolution")
69
56
 
70
57
  # Try to resolve entities using fuzzy matching
71
58
  test_names = ["Bob Johnson", "R Johnson", "Robert J", "Global Ind", "GII"]
72
59
 
73
60
  test_names.each do |name|
74
- resolved = entity_service.resolve(name, type: nil)
61
+ resolved = entity_service.resolve(name, kind: nil)
75
62
  if resolved
76
- puts "Resolved '#{name}' -> #{resolved.canonical_name} (#{resolved.entity_type})"
63
+ puts "Resolved '#{name}' -> #{resolved.name} (#{resolved.kind})"
77
64
  else
78
65
  puts "Could not resolve '#{name}'"
79
66
  end
@@ -83,81 +70,82 @@ end
83
70
  puts "\nUsing resolve_or_create:"
84
71
  new_person = entity_service.resolve_or_create(
85
72
  "Maria Garcia",
86
- type: :person,
73
+ kind: :person,
87
74
  description: "New employee"
88
75
  )
89
- puts "Result: #{new_person.canonical_name} (new: #{new_person.created_at == new_person.updated_at})"
76
+ puts "Result: #{new_person.name} (new: #{new_person.created_at == new_person.updated_at})"
90
77
 
91
- # Section 3: Managing Aliases
92
- puts "\n--- Section 3: Managing Aliases ---\n"
78
+ demo_section("Section 3: Managing Aliases")
93
79
 
94
80
  # Add more aliases to an existing entity
95
- entity_service.add_alias(person.id, "Robert J.", alias_type: :name, confidence: 0.9)
96
- entity_service.add_alias(person.id, "rjohnson@example.com", alias_type: :email, confidence: 1.0)
81
+ entity_service.add_alias(person.id, "Robert J.", kind: :name, confidence: 0.9)
82
+ entity_service.add_alias(person.id, "rjohnson@example.com", kind: :email, confidence: 1.0)
97
83
 
98
84
  person.reload
99
- puts "Updated aliases for #{person.canonical_name}:"
85
+ puts "Updated aliases for #{person.name}:"
100
86
  person.aliases.each do |a|
101
- puts " - #{a.alias_text} (#{a.alias_type}, confidence: #{a.confidence})"
87
+ puts " - #{a.name} (#{a.kind}, confidence: #{a.confidence})"
102
88
  end
103
89
 
104
- # Section 4: Merging Duplicate Entities
105
- puts "\n--- Section 4: Merging Entities ---\n"
90
+ demo_section("Section 4: Merging Entities")
106
91
 
107
92
  # Create a duplicate entity (simulating data entry error)
93
+ # Using "Robert Johnsen" (misspelling) to demonstrate fuzzy matching
108
94
  duplicate = entity_service.create(
109
- "Bob Johnson",
110
- type: :person,
111
- description: "Possible duplicate of Robert Johnson"
95
+ "Robert Johnsen",
96
+ kind: :person,
97
+ description: "Possible duplicate of Robert Johnson (typo)"
112
98
  )
113
- puts "Created potential duplicate: #{duplicate.canonical_name} (ID: #{duplicate.id})"
99
+ puts "Created potential duplicate: #{duplicate.name} (ID: #{duplicate.id})"
114
100
 
115
101
  # Find potential duplicates
116
102
  puts "\nSearching for duplicates:"
117
103
  duplicates = entity_service.find_duplicates(threshold: 0.8)
118
- duplicates.each do |dup_pair|
119
- puts " Potential duplicate: #{dup_pair[:entity1].canonical_name} <-> #{dup_pair[:entity2].canonical_name}"
120
- puts " Similarity: #{dup_pair[:similarity]}"
104
+ if duplicates.empty?
105
+ puts " No duplicates found above threshold 0.8"
106
+ else
107
+ duplicates.each do |dup_pair|
108
+ puts " Potential duplicate: #{dup_pair[:entity1].name} (ID: #{dup_pair[:entity1].id}) <-> #{dup_pair[:entity2].name} (ID: #{dup_pair[:entity2].id})"
109
+ puts " Similarity: #{dup_pair[:similarity].round(3)}"
110
+ end
121
111
  end
122
112
 
123
113
  # Merge the duplicate into the canonical entity
124
114
  puts "\nMerging entities..."
125
115
  entity_service.merge(person.id, duplicate.id)
126
- puts "Merged '#{duplicate.canonical_name}' into '#{person.canonical_name}'"
116
+ puts "Merged '#{duplicate.name}' into '#{person.name}'"
127
117
 
128
118
  # Verify the duplicate is marked as merged
129
119
  duplicate.reload
130
120
  puts "Duplicate status: #{duplicate.resolution_status}"
131
- puts "Merged into: #{duplicate.merged_into_id}"
121
+ puts "Canonical ID: #{duplicate.canonical_id}"
132
122
 
133
- # Section 5: Searching Entities
134
- puts "\n--- Section 5: Searching Entities ---\n"
123
+ demo_section("Section 5: Searching Entities")
135
124
 
136
125
  # Create more entities for search demo
137
- entity_service.create("Jennifer Wilson", type: :person, description: "Marketing Manager")
138
- entity_service.create("John Williams", type: :person, description: "Software Engineer")
139
- entity_service.create("Wilson & Associates", type: :organization, description: "Law firm")
126
+ entity_service.create("Jennifer Wilson", kind: :person, description: "Marketing Manager")
127
+ entity_service.create("John Williams", kind: :person, description: "Software Engineer")
128
+ entity_service.create("Wilson & Associates", kind: :organization, description: "Law firm")
140
129
 
141
130
  # Text search
142
131
  puts "Search results for 'Wilson':"
143
132
  results = entity_service.search("Wilson")
144
133
  results.each do |entity|
145
- puts " - #{entity.canonical_name} (#{entity.entity_type})"
134
+ puts " - #{entity.name} (#{entity.kind})"
146
135
  end
147
136
 
148
137
  # Filter by type
149
138
  puts "\nPeople only:"
150
- entity_service.people.each do |entity|
151
- puts " - #{entity.canonical_name}"
139
+ entity_service.by_kind("person").each do |entity|
140
+ puts " - #{entity.name}"
152
141
  end
153
142
 
154
143
  puts "\nOrganizations only:"
155
- entity_service.organizations.each do |entity|
156
- puts " - #{entity.canonical_name}"
144
+ entity_service.by_kind("organization").each do |entity|
145
+ puts " - #{entity.name}"
157
146
  end
158
147
 
159
- # Section 6: Entity Timeline
160
- puts "\n--- Section 6: Entity Timeline ---\n"
148
+ demo_section("Section 6: Entity Timeline")
161
149
 
162
150
  # Create some facts about Bob to build a timeline
163
151
  fact_service.create(
@@ -189,28 +177,25 @@ fact_service.create(
189
177
  )
190
178
 
191
179
  # Build timeline for the person
192
- puts "Timeline for #{person.canonical_name}:"
180
+ puts "Timeline for #{person.name}:"
193
181
  timeline = entity_service.timeline_for(person.id, from: Date.new(2017, 1, 1), to: Date.today)
194
182
  timeline.each do |entry|
195
183
  date_range = entry[:invalid_at] ? "#{entry[:valid_at]} - #{entry[:invalid_at]}" : "#{entry[:valid_at]} - present"
196
184
  puts " [#{date_range}]"
197
- puts " #{entry[:fact_text]}"
185
+ puts " #{entry[:text]}"
198
186
  end
199
187
 
200
- # Section 7: Statistics
201
- puts "\n--- Section 7: Entity Statistics ---\n"
188
+ demo_section("Section 7: Entity Statistics")
202
189
 
203
190
  stats = entity_service.stats
204
191
  puts "Total entities: #{stats[:total]}"
205
- puts "By type:"
206
- stats[:by_type].each do |type, count|
207
- puts " #{type}: #{count}"
192
+ puts "By kind:"
193
+ stats[:by_kind].each do |kind, count|
194
+ puts " #{kind}: #{count}"
208
195
  end
209
196
  puts "By resolution status:"
210
197
  stats[:by_status].each do |status, count|
211
198
  puts " #{status}: #{count}"
212
199
  end
213
200
 
214
- puts "\n" + "=" * 60
215
- puts "Entity Management Demo Complete!"
216
- puts "=" * 60
201
+ demo_footer("Entity Management Demo Complete!")
@@ -10,55 +10,44 @@
10
10
  # - Detecting fact changes over time
11
11
  # - Building temporal diffs
12
12
 
13
- require "bundler/setup"
14
- require "fact_db"
13
+ require_relative "utilities"
15
14
 
16
- FactDb.configure do |config|
17
- config.database_url = ENV.fetch("DATABASE_URL", "postgres://#{ENV['USER']}@localhost/fact_db_demo")
18
- end
19
-
20
- # Ensure database tables exist
21
- FactDb::Database.migrate!
22
-
23
- clock = FactDb.new
24
- entity_service = clock.entity_service
25
- fact_service = clock.fact_service
15
+ demo_setup!("FactDb Temporal Queries Demo")
16
+ demo_configure_logging(__FILE__)
26
17
 
27
- puts "=" * 60
28
- puts "FactDb Temporal Queries Demo"
29
- puts "=" * 60
18
+ facts = FactDb.new
19
+ entity_service = facts.entity_service
20
+ fact_service = facts.fact_service
30
21
 
31
- # Setup: Create entities for our scenario
32
- puts "\n--- Setup: Creating Entities ---\n"
22
+ demo_section("Setup: Creating Entities")
33
23
 
34
24
  company = entity_service.create(
35
25
  "TechCorp Ltd",
36
- type: :organization,
26
+ kind: :organization,
37
27
  description: "Technology company"
38
28
  )
39
29
 
40
30
  ceo = entity_service.create(
41
31
  "Alice Chen",
42
- type: :person,
32
+ kind: :person,
43
33
  description: "Executive"
44
34
  )
45
35
 
46
36
  new_ceo = entity_service.create(
47
37
  "David Park",
48
- type: :person,
38
+ kind: :person,
49
39
  description: "Executive"
50
40
  )
51
41
 
52
42
  cfo = entity_service.create(
53
43
  "Sarah Miller",
54
- type: :person,
44
+ kind: :person,
55
45
  description: "Finance executive"
56
46
  )
57
47
 
58
- puts "Created entities: #{company.canonical_name}, #{ceo.canonical_name}, #{new_ceo.canonical_name}, #{cfo.canonical_name}"
48
+ puts "Created entities: #{company.name}, #{ceo.name}, #{new_ceo.name}, #{cfo.name}"
59
49
 
60
- # Section 1: Creating Temporal Facts
61
- puts "\n--- Section 1: Creating Temporal Facts ---\n"
50
+ demo_section("Section 1: Creating Temporal Facts")
62
51
 
63
52
  # Fact with open-ended validity (still true)
64
53
  fact1 = fact_service.create(
@@ -66,7 +55,7 @@ fact1 = fact_service.create(
66
55
  valid_at: Date.new(2015, 1, 1),
67
56
  mentions: [{ entity_id: company.id, role: :subject, text: "TechCorp Ltd" }]
68
57
  )
69
- puts "Created: #{fact1.fact_text}"
58
+ puts "Created: #{fact1.text}"
70
59
  puts " Valid: #{fact1.valid_at} - present"
71
60
 
72
61
  # Fact with closed validity (historical)
@@ -79,7 +68,7 @@ fact2 = fact_service.create(
79
68
  { entity_id: company.id, role: :object, text: "TechCorp Ltd" }
80
69
  ]
81
70
  )
82
- puts "\nCreated: #{fact2.fact_text}"
71
+ puts "\nCreated: #{fact2.text}"
83
72
  puts " Valid: #{fact2.valid_at} - #{fact2.invalid_at}"
84
73
 
85
74
  # Current CEO
@@ -91,7 +80,7 @@ fact3 = fact_service.create(
91
80
  { entity_id: company.id, role: :object, text: "TechCorp Ltd" }
92
81
  ]
93
82
  )
94
- puts "\nCreated: #{fact3.fact_text}"
83
+ puts "\nCreated: #{fact3.text}"
95
84
  puts " Valid: #{fact3.valid_at} - present"
96
85
 
97
86
  # Another current fact
@@ -103,11 +92,10 @@ fact4 = fact_service.create(
103
92
  { entity_id: company.id, role: :object, text: "TechCorp Ltd" }
104
93
  ]
105
94
  )
106
- puts "\nCreated: #{fact4.fact_text}"
95
+ puts "\nCreated: #{fact4.text}"
107
96
  puts " Valid: #{fact4.valid_at} - present"
108
97
 
109
- # Section 2: Point-in-Time Queries
110
- puts "\n--- Section 2: Point-in-Time Queries ---\n"
98
+ demo_section("Section 2: Point-in-Time Queries")
111
99
 
112
100
  # Query facts valid at different dates
113
101
  dates_to_query = [
@@ -121,24 +109,22 @@ dates_to_query.each do |date|
121
109
  puts "\nFacts about TechCorp on #{date}:"
122
110
  facts = fact_service.facts_at(date, entity: company.id)
123
111
  facts.each do |fact|
124
- puts " - #{fact.fact_text}"
112
+ puts " - #{fact.text}"
125
113
  end
126
114
  end
127
115
 
128
- # Section 3: Current vs Historical Facts
129
- puts "\n--- Section 3: Current vs Historical Facts ---\n"
116
+ demo_section("Section 3: Current vs Historical Facts")
130
117
 
131
118
  puts "Currently valid facts about TechCorp:"
132
119
  current = fact_service.current_facts(entity: company.id)
133
- current.each { |f| puts " - #{f.fact_text}" }
120
+ current.each { |f| puts " - #{f.text}" }
134
121
 
135
122
  puts "\nAll historical facts:"
136
123
  FactDb::Models::Fact.historical.each do |fact|
137
- puts " - #{fact.fact_text} (ended: #{fact.invalid_at})"
124
+ puts " - #{fact.text} (ended: #{fact.invalid_at})"
138
125
  end
139
126
 
140
- # Section 4: Superseding Facts
141
- puts "\n--- Section 4: Superseding Facts ---\n"
127
+ demo_section("Section 4: Superseding Facts")
142
128
 
143
129
  # Company valuation that changes over time
144
130
  valuation_2020 = fact_service.create(
@@ -146,7 +132,7 @@ valuation_2020 = fact_service.create(
146
132
  valid_at: Date.new(2020, 1, 1),
147
133
  mentions: [{ entity_id: company.id, role: :subject, text: "TechCorp Ltd" }]
148
134
  )
149
- puts "Created valuation fact: #{valuation_2020.fact_text}"
135
+ puts "Created valuation fact: #{valuation_2020.text}"
150
136
 
151
137
  # Supersede with new valuation
152
138
  valuation_2023 = fact_service.supersede(
@@ -155,15 +141,14 @@ valuation_2023 = fact_service.supersede(
155
141
  valid_at: Date.new(2023, 1, 1),
156
142
  mentions: [{ entity_id: company.id, role: :subject, text: "TechCorp Ltd" }]
157
143
  )
158
- puts "\nSuperseded with: #{valuation_2023.fact_text}"
144
+ puts "\nSuperseded with: #{valuation_2023.text}"
159
145
 
160
146
  # Check the old fact status
161
147
  valuation_2020.reload
162
148
  puts "\nOriginal fact status: #{valuation_2020.status}"
163
149
  puts "Original fact now invalid at: #{valuation_2020.invalid_at}"
164
150
 
165
- # Section 5: Temporal Timeline
166
- puts "\n--- Section 5: Temporal Timeline for Company ---\n"
151
+ demo_section("Section 5: Temporal Timeline")
167
152
 
168
153
  timeline = fact_service.timeline(
169
154
  entity_id: company.id,
@@ -171,15 +156,14 @@ timeline = fact_service.timeline(
171
156
  to: Date.today
172
157
  )
173
158
 
174
- puts "Complete timeline for #{company.canonical_name}:"
159
+ puts "Complete timeline for #{company.name}:"
175
160
  timeline.each do |entry|
176
161
  end_date = entry[:invalid_at] || "present"
177
162
  status_indicator = entry[:status] == "canonical" ? "" : " [#{entry[:status]}]"
178
- puts " #{entry[:valid_at]} - #{end_date}: #{entry[:fact_text]}#{status_indicator}"
163
+ puts " #{entry[:valid_at]} - #{end_date}: #{entry[:text]}#{status_indicator}"
179
164
  end
180
165
 
181
- # Section 6: Temporal Diff
182
- puts "\n--- Section 6: Temporal Diff ---\n"
166
+ demo_section("Section 6: Temporal Diff")
183
167
 
184
168
  temporal_query = FactDb::Temporal::Query.new
185
169
 
@@ -193,53 +177,49 @@ diff = temporal_query.diff(
193
177
 
194
178
  if diff[:added].any?
195
179
  puts "\n Added:"
196
- diff[:added].each { |f| puts " + #{f.fact_text}" }
180
+ diff[:added].each { |f| puts " + #{f.text}" }
197
181
  end
198
182
 
199
183
  if diff[:removed].any?
200
184
  puts "\n Removed:"
201
- diff[:removed].each { |f| puts " - #{f.fact_text}" }
185
+ diff[:removed].each { |f| puts " - #{f.text}" }
202
186
  end
203
187
 
204
188
  if diff[:unchanged].any?
205
189
  puts "\n Unchanged:"
206
- diff[:unchanged].each { |f| puts " = #{f.fact_text}" }
190
+ diff[:unchanged].each { |f| puts " = #{f.text}" }
207
191
  end
208
192
 
209
- # Section 7: Facts Created/Invalidated in Date Range
210
- puts "\n--- Section 7: Facts by Creation/Invalidation Period ---\n"
193
+ demo_section("Section 7: Facts Created/Invalidated in Date Range")
211
194
 
212
195
  puts "Facts that became valid in 2025:"
213
196
  new_facts = temporal_query.facts_created_between(
214
197
  from: Date.new(2025, 1, 1),
215
198
  to: Date.new(2025, 12, 31)
216
199
  )
217
- new_facts.each { |f| puts " - #{f.fact_text} (valid from #{f.valid_at})" }
200
+ new_facts.each { |f| puts " - #{f.text} (valid from #{f.valid_at})" }
218
201
 
219
202
  puts "\nFacts that ended in 2024:"
220
203
  ended_facts = temporal_query.facts_invalidated_between(
221
204
  from: Date.new(2024, 1, 1),
222
205
  to: Date.new(2024, 12, 31)
223
206
  )
224
- ended_facts.each { |f| puts " - #{f.fact_text} (ended #{f.invalid_at})" }
207
+ ended_facts.each { |f| puts " - #{f.text} (ended #{f.invalid_at})" }
225
208
 
226
- # Section 8: Entity Role Queries
227
- puts "\n--- Section 8: Query by Entity Role ---\n"
209
+ demo_section("Section 8: Entity Role Queries")
228
210
 
229
211
  puts "Facts where TechCorp is the subject:"
230
212
  subject_facts = temporal_query.facts_with_entity_role(
231
213
  entity_id: company.id,
232
214
  role: :subject
233
215
  )
234
- subject_facts.each { |f| puts " - #{f.fact_text}" }
216
+ subject_facts.each { |f| puts " - #{f.text}" }
235
217
 
236
218
  puts "\nFacts where TechCorp is the object:"
237
219
  object_facts = temporal_query.facts_with_entity_role(
238
220
  entity_id: company.id,
239
221
  role: :object
240
222
  )
241
- object_facts.each { |f| puts " - #{f.fact_text}" }
223
+ object_facts.each { |f| puts " - #{f.text}" }
242
224
 
243
- puts "\n" + "=" * 60
244
- puts "Temporal Queries Demo Complete!"
245
- puts "=" * 60
225
+ demo_footer