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,252 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Introspection Example for FactDb
5
+ #
6
+ # This example demonstrates:
7
+ # - Schema introspection - discovering what the system knows
8
+ # - Topic introspection - examining specific entities
9
+ # - Query suggestions based on available data
10
+ # - Retrieval strategy recommendations
11
+ # - New service methods for entity analysis
12
+
13
+ require_relative "utilities"
14
+
15
+ demo_setup!("FactDb Introspection Demo")
16
+ demo_configure_logging(__FILE__)
17
+
18
+ facts = FactDb.new
19
+ entity_service = facts.entity_service
20
+ fact_service = facts.fact_service
21
+
22
+ demo_section("Setup: Creating Sample Data")
23
+
24
+ # Create entities
25
+ maria = entity_service.resolve_or_create(
26
+ "Maria Santos",
27
+ kind: :person,
28
+ aliases: ["M. Santos"],
29
+ description: "Engineering Manager"
30
+ )
31
+
32
+ raj = entity_service.resolve_or_create(
33
+ "Raj Patel",
34
+ kind: :person,
35
+ description: "Senior Engineer"
36
+ )
37
+
38
+ sarah = entity_service.resolve_or_create(
39
+ "Sarah Kim",
40
+ kind: :person,
41
+ description: "Software Engineer"
42
+ )
43
+
44
+ techcorp = entity_service.resolve_or_create(
45
+ "TechCorp",
46
+ kind: :organization,
47
+ description: "Software company"
48
+ )
49
+
50
+ austin = entity_service.resolve_or_create(
51
+ "Austin",
52
+ kind: :place,
53
+ description: "City in Texas"
54
+ )
55
+
56
+ puts "Created 5 entities"
57
+
58
+ # Create facts with various relationships
59
+ fact_service.create(
60
+ "Maria Santos is Engineering Manager at TechCorp",
61
+ valid_at: Date.new(2023, 1, 1),
62
+ extraction_method: :manual,
63
+ confidence: 1.0,
64
+ mentions: [
65
+ { entity_id: maria.id, role: :subject, text: "Maria Santos" },
66
+ { entity_id: techcorp.id, role: :object, text: "TechCorp" }
67
+ ]
68
+ )
69
+
70
+ fact_service.create(
71
+ "Raj Patel reports to Maria Santos",
72
+ valid_at: Date.new(2023, 6, 1),
73
+ extraction_method: :manual,
74
+ mentions: [
75
+ { entity_id: raj.id, role: :subject, text: "Raj Patel" },
76
+ { entity_id: maria.id, role: :object, text: "Maria Santos" }
77
+ ]
78
+ )
79
+
80
+ fact_service.create(
81
+ "Sarah Kim reports to Maria Santos",
82
+ valid_at: Date.new(2024, 1, 1),
83
+ extraction_method: :manual,
84
+ mentions: [
85
+ { entity_id: sarah.id, role: :subject, text: "Sarah Kim" },
86
+ { entity_id: maria.id, role: :object, text: "Maria Santos" }
87
+ ]
88
+ )
89
+
90
+ # Historical fact (superseded)
91
+ old_role = fact_service.create(
92
+ "Maria Santos was Senior Engineer at TechCorp",
93
+ valid_at: Date.new(2020, 1, 1),
94
+ invalid_at: Date.new(2022, 12, 31),
95
+ extraction_method: :manual,
96
+ status: :superseded,
97
+ mentions: [
98
+ { entity_id: maria.id, role: :subject, text: "Maria Santos" },
99
+ { entity_id: techcorp.id, role: :object, text: "TechCorp" }
100
+ ]
101
+ )
102
+
103
+ fact_service.create(
104
+ "Maria Santos works at Austin office",
105
+ valid_at: Date.new(2023, 1, 1),
106
+ extraction_method: :rule_based,
107
+ mentions: [
108
+ { entity_id: maria.id, role: :subject, text: "Maria Santos" },
109
+ { entity_id: austin.id, role: :location, text: "Austin" }
110
+ ]
111
+ )
112
+
113
+ puts "Created facts with various relationships and history"
114
+
115
+ demo_section("Section 1: Schema Introspection - facts.introspect()")
116
+
117
+ schema = facts.introspect
118
+ puts "\nSystem Capabilities:"
119
+ schema[:capabilities].each { |c| puts " - #{c}" }
120
+
121
+ puts "\nEntity Types in Database:"
122
+ schema[:entity_kinds].each { |t| puts " - #{t}" }
123
+
124
+ puts "\nAvailable Fact Statuses:"
125
+ schema[:fact_statuses].each { |s| puts " - #{s}" }
126
+
127
+ puts "\nExtraction Methods:"
128
+ schema[:extraction_methods].each { |m| puts " - #{m}" }
129
+
130
+ puts "\nSupported Output Formats:"
131
+ schema[:output_formats].each { |f| puts " - #{f}" }
132
+
133
+ puts "\nRetrieval Strategies:"
134
+ schema[:retrieval_strategies].each { |s| puts " - #{s}" }
135
+
136
+ puts "\nStatistics:"
137
+ puts JSON.pretty_generate(schema[:statistics])
138
+
139
+ demo_section("Section 2: Topic Introspection - facts.introspect('Maria Santos')")
140
+
141
+ maria_info = facts.introspect("Maria Santos")
142
+ if maria_info
143
+ puts "\nEntity Information:"
144
+ puts " Name: #{maria_info[:entity][:name]}"
145
+ puts " Type: #{maria_info[:entity][:kind]}"
146
+ puts " Status: #{maria_info[:entity][:resolution_status]}"
147
+
148
+ puts "\nFact Coverage:"
149
+ puts " Canonical: #{maria_info[:coverage][:facts][:canonical]}"
150
+ puts " Superseded: #{maria_info[:coverage][:facts][:superseded]}"
151
+ puts " Corroborated: #{maria_info[:coverage][:facts][:corroborated]}"
152
+ puts " Synthesized: #{maria_info[:coverage][:facts][:synthesized]}"
153
+
154
+ puts "\nTimespan:"
155
+ puts " From: #{maria_info[:coverage][:timespan][:from]}"
156
+ puts " To: #{maria_info[:coverage][:timespan][:to]}"
157
+
158
+ puts "\nRelationship Types:"
159
+ maria_info[:relationships].each { |r| puts " - #{r}" }
160
+
161
+ puts "\nSuggested Queries:"
162
+ maria_info[:suggested_queries].each { |q| puts " - #{q}" }
163
+ else
164
+ puts "Entity not found"
165
+ end
166
+
167
+ demo_section("Section 3: Query Suggestions - facts.suggest_queries()")
168
+
169
+ %w[Maria\ Santos Raj\ Patel TechCorp].each do |topic|
170
+ suggestions = facts.suggest_queries(topic)
171
+ puts "\nSuggested queries for '#{topic}':"
172
+ if suggestions.empty?
173
+ puts " (no suggestions available)"
174
+ else
175
+ suggestions.each { |s| puts " - #{s}" }
176
+ end
177
+ end
178
+
179
+ demo_section("Section 4: Strategy Suggestions")
180
+
181
+ test_queries = [
182
+ "What happened last week?",
183
+ "Who works at TechCorp?",
184
+ "Find similar projects",
185
+ "Current team members",
186
+ "Changes since January"
187
+ ]
188
+
189
+ test_queries.each do |query|
190
+ strategies = facts.suggest_strategies(query)
191
+ puts "\nQuery: \"#{query}\""
192
+ puts " Recommended strategies:"
193
+ strategies.each do |s|
194
+ puts " - #{s[:strategy]}: #{s[:description]}"
195
+ end
196
+ end
197
+
198
+ demo_section("Section 5: New Entity Service Methods")
199
+
200
+ puts "\n--- relationship_types ---"
201
+ puts "All relationship types in database:"
202
+ all_relationships = entity_service.relationship_types
203
+ all_relationships.each { |r| puts " - #{r}" }
204
+
205
+ puts "\n--- relationship_types_for(entity_id) ---"
206
+ puts "Relationship types for Maria Santos:"
207
+ maria_relationships = entity_service.relationship_types_for(maria.id)
208
+ maria_relationships.each { |r| puts " - #{r}" }
209
+
210
+ puts "\n--- timespan_for(entity_id) ---"
211
+ puts "Timespan of facts for Maria Santos:"
212
+ timespan = entity_service.timespan_for(maria.id)
213
+ puts " From: #{timespan[:from]}"
214
+ puts " To: #{timespan[:to]}"
215
+
216
+ demo_section("Section 6: New Fact Service Methods")
217
+
218
+ puts "\n--- fact_stats(entity_id) ---"
219
+ puts "Fact statistics for Maria Santos:"
220
+ maria_stats = fact_service.fact_stats(maria.id)
221
+ maria_stats.each { |status, count| puts " #{status}: #{count}" }
222
+
223
+ puts "\nFact statistics for all facts:"
224
+ all_stats = fact_service.fact_stats
225
+ all_stats.each { |status, count| puts " #{status}: #{count}" }
226
+
227
+ demo_section("Section 7: Practical Use Case - LLM Context Building")
228
+
229
+ puts "\nBuilding context for an LLM query about Maria Santos:\n"
230
+
231
+ # Step 1: Introspect the topic
232
+ topic_info = facts.introspect("Maria Santos")
233
+
234
+ puts "1. Entity identified: #{topic_info[:entity][:name]} (#{topic_info[:entity][:kind]})"
235
+ puts " Coverage: #{topic_info[:coverage][:facts][:canonical]} current facts, #{topic_info[:coverage][:facts][:superseded]} historical"
236
+
237
+ # Step 2: Get facts in LLM-friendly format
238
+ puts "\n2. Facts in text format for LLM consumption:"
239
+ facts_text = facts.current_facts_for(maria.id, format: :text)
240
+ puts facts_text
241
+
242
+ # Step 3: Get facts in structured format for reasoning
243
+ puts "\n3. Facts as triples for structured reasoning:"
244
+ facts_triples = facts.current_facts_for(maria.id, format: :triples)
245
+ facts_triples.take(5).each { |t| puts " #{t.inspect}" }
246
+ puts " ..." if facts_triples.size > 5
247
+
248
+ # Step 4: Suggest follow-up queries
249
+ puts "\n4. Suggested follow-up queries:"
250
+ topic_info[:suggested_queries].each { |q| puts " - #{q}" }
251
+
252
+ demo_footer
@@ -10,33 +10,26 @@
10
10
  # - Auditing changes with temporal queries
11
11
  # - Detecting conflicts in employee data
12
12
 
13
- require "bundler/setup"
14
- require "fact_db"
13
+ require_relative "utilities"
14
+
15
+ demo_setup!("HR Knowledge Management System Demo")
16
+ demo_configure_logging(__FILE__)
15
17
 
16
18
  FactDb.configure do |config|
17
- config.database_url = ENV.fetch("DATABASE_URL", "postgres://#{ENV['USER']}@localhost/fact_db_demo")
18
19
  config.default_extractor = :manual
19
20
  end
20
21
 
21
- # Ensure database tables exist
22
- FactDb::Database.migrate!
23
-
24
- clock = FactDb.new
25
- entity_service = clock.entity_service
26
- fact_service = clock.fact_service
27
- content_service = clock.content_service
28
-
29
- puts "=" * 60
30
- puts "HR Knowledge Management System Demo"
31
- puts "=" * 60
22
+ facts = FactDb.new
23
+ entity_service = facts.entity_service
24
+ fact_service = facts.fact_service
25
+ source_service = facts.source_service
32
26
 
33
- # Section 1: Setup Company Structure
34
- puts "\n--- Section 1: Setting Up Organization ---\n"
27
+ demo_section("Section 1: Setting Up Organization")
35
28
 
36
29
  # Create the company
37
30
  company = entity_service.create(
38
31
  "Innovate Corp",
39
- type: :organization,
32
+ kind: :organization,
40
33
  description: "Technology company specializing in AI solutions",
41
34
  attributes: { industry: "Technology", founded: "2010" }
42
35
  )
@@ -44,54 +37,53 @@ company = entity_service.create(
44
37
  # Create departments
45
38
  engineering = entity_service.create(
46
39
  "Engineering Department",
47
- type: :organization,
40
+ kind: :organization,
48
41
  description: "Software engineering team",
49
42
  attributes: { parent: company.id }
50
43
  )
51
44
 
52
45
  product = entity_service.create(
53
46
  "Product Department",
54
- type: :organization,
47
+ kind: :organization,
55
48
  description: "Product management team",
56
49
  attributes: { parent: company.id }
57
50
  )
58
51
 
59
52
  hr_dept = entity_service.create(
60
53
  "Human Resources",
61
- type: :organization,
54
+ kind: :organization,
62
55
  description: "HR team",
63
56
  attributes: { parent: company.id }
64
57
  )
65
58
 
66
- puts "Created company: #{company.canonical_name}"
67
- puts "Created departments: #{engineering.canonical_name}, #{product.canonical_name}, #{hr_dept.canonical_name}"
59
+ puts "Created company: #{company.name}"
60
+ puts "Created departments: #{engineering.name}, #{product.name}, #{hr_dept.name}"
68
61
 
69
62
  # Create locations
70
63
  hq = entity_service.create(
71
64
  "San Francisco HQ",
72
- type: :place,
65
+ kind: :place,
73
66
  aliases: ["SF Office", "Headquarters"],
74
67
  attributes: { city: "San Francisco", state: "CA" }
75
68
  )
76
69
 
77
70
  remote_office = entity_service.create(
78
71
  "Austin Office",
79
- type: :place,
72
+ kind: :place,
80
73
  aliases: ["Austin TX Office"],
81
74
  attributes: { city: "Austin", state: "TX" }
82
75
  )
83
76
 
84
- puts "Created locations: #{hq.canonical_name}, #{remote_office.canonical_name}"
77
+ puts "Created locations: #{hq.name}, #{remote_office.name}"
85
78
 
86
- # Section 2: Create Employee Profiles
87
- puts "\n--- Section 2: Creating Employee Profiles ---\n"
79
+ demo_section("Section 2: Create Employee Profiles")
88
80
 
89
81
  employees = {}
90
82
 
91
83
  # CEO
92
84
  employees[:ceo] = entity_service.create(
93
85
  "Katherine Rodriguez",
94
- type: :person,
86
+ kind: :person,
95
87
  aliases: ["Kate Rodriguez", "K. Rodriguez"],
96
88
  attributes: { employee_id: "EMP001", email: "krodriguez@innovatecorp.com" },
97
89
  description: "Chief Executive Officer"
@@ -100,7 +92,7 @@ employees[:ceo] = entity_service.create(
100
92
  # VP Engineering
101
93
  employees[:vp_eng] = entity_service.create(
102
94
  "Marcus Chen",
103
- type: :person,
95
+ kind: :person,
104
96
  aliases: ["Marc Chen"],
105
97
  attributes: { employee_id: "EMP002", email: "mchen@innovatecorp.com" },
106
98
  description: "VP of Engineering"
@@ -109,7 +101,7 @@ employees[:vp_eng] = entity_service.create(
109
101
  # Senior Engineer
110
102
  employees[:senior_eng] = entity_service.create(
111
103
  "Priya Sharma",
112
- type: :person,
104
+ kind: :person,
113
105
  attributes: { employee_id: "EMP003", email: "psharma@innovatecorp.com" },
114
106
  description: "Senior Software Engineer"
115
107
  )
@@ -117,7 +109,7 @@ employees[:senior_eng] = entity_service.create(
117
109
  # Junior Engineer (will be promoted)
118
110
  employees[:junior_eng] = entity_service.create(
119
111
  "Alex Kim",
120
- type: :person,
112
+ kind: :person,
121
113
  attributes: { employee_id: "EMP004", email: "akim@innovatecorp.com" },
122
114
  description: "Software Engineer"
123
115
  )
@@ -125,7 +117,7 @@ employees[:junior_eng] = entity_service.create(
125
117
  # Product Manager
126
118
  employees[:pm] = entity_service.create(
127
119
  "Jordan Taylor",
128
- type: :person,
120
+ kind: :person,
129
121
  attributes: { employee_id: "EMP005", email: "jtaylor@innovatecorp.com" },
130
122
  description: "Product Manager"
131
123
  )
@@ -133,18 +125,17 @@ employees[:pm] = entity_service.create(
133
125
  # HR Manager
134
126
  employees[:hr_mgr] = entity_service.create(
135
127
  "Michelle Brown",
136
- type: :person,
128
+ kind: :person,
137
129
  attributes: { employee_id: "EMP006", email: "mbrown@innovatecorp.com" },
138
130
  description: "HR Manager"
139
131
  )
140
132
 
141
133
  puts "Created #{employees.length} employee profiles"
142
134
 
143
- # Section 3: Record Initial Employment Facts
144
- puts "\n--- Section 3: Recording Employment History ---\n"
135
+ demo_section("Section 3: Record Initial Employment Facts")
145
136
 
146
137
  # Ingest an onboarding document
147
- onboarding_doc = content_service.create(
138
+ onboarding_doc = source_service.create(
148
139
  <<~DOC,
149
140
  EMPLOYEE ONBOARDING RECORDS - 2020-2024
150
141
 
@@ -155,7 +146,7 @@ onboarding_doc = content_service.create(
155
146
  Jordan Taylor - Hired as Associate PM on February 1, 2022
156
147
  Michelle Brown - Hired as HR Coordinator on April 1, 2021
157
148
  DOC
158
- type: :document,
149
+ kind: :document,
159
150
  title: "Historical Onboarding Records"
160
151
  )
161
152
 
@@ -168,7 +159,7 @@ ceo_employment = fact_service.create(
168
159
  { entity_id: company.id, role: :object, text: "Innovate Corp" }
169
160
  ]
170
161
  )
171
- ceo_employment.add_source(content: onboarding_doc, type: :primary)
162
+ ceo_employment.add_source(source: onboarding_doc, kind: :primary)
172
163
 
173
164
  ceo_location = fact_service.create(
174
165
  "Katherine Rodriguez works at San Francisco HQ",
@@ -259,11 +250,10 @@ hr_current = fact_service.create(
259
250
 
260
251
  puts "Recorded employment history facts"
261
252
 
262
- # Section 4: Process a Promotion
263
- puts "\n--- Section 4: Processing a Promotion ---\n"
253
+ demo_section("Section 4: Process a Promotion")
264
254
 
265
255
  # Ingest the promotion memo
266
- promotion_memo = content_service.create(
256
+ promotion_memo = source_service.create(
267
257
  <<~MEMO,
268
258
  INTERNAL MEMO
269
259
  Date: January 8, 2026
@@ -277,7 +267,7 @@ promotion_memo = content_service.create(
277
267
 
278
268
  Congratulations Alex!
279
269
  MEMO
280
- type: :document,
270
+ kind: :document,
281
271
  title: "Promotion Memo - Alex Kim"
282
272
  )
283
273
 
@@ -291,17 +281,16 @@ promoted_fact = fact_service.supersede(
291
281
  { entity_id: engineering.id, role: :object, text: "Engineering Department" }
292
282
  ]
293
283
  )
294
- promoted_fact.add_source(content: promotion_memo, type: :primary)
284
+ promoted_fact.add_source(source: promotion_memo, kind: :primary)
295
285
 
296
286
  puts "Promoted Alex Kim from Junior Developer to Software Engineer"
297
287
  puts "Previous fact (#{junior_original.id}) now superseded"
298
288
  puts "New fact ID: #{promoted_fact.id}"
299
289
 
300
- # Section 5: Record a Transfer
301
- puts "\n--- Section 5: Recording a Transfer ---\n"
290
+ demo_section("Section 5: Record a Transfer")
302
291
 
303
292
  # Jordan is transferring to Austin
304
- transfer_memo = content_service.create(
293
+ transfer_memo = source_service.create(
305
294
  <<~MEMO,
306
295
  INTERNAL MEMO
307
296
  Date: January 10, 2026
@@ -311,7 +300,7 @@ transfer_memo = content_service.create(
311
300
  effective February 1, 2026. Jordan will continue in the
312
301
  Product Manager role but will lead our Texas expansion efforts.
313
302
  MEMO
314
- type: :document,
303
+ kind: :document,
315
304
  title: "Transfer Notice - Jordan Taylor"
316
305
  )
317
306
 
@@ -336,52 +325,48 @@ jordan_austin_location = fact_service.create(
336
325
  { entity_id: remote_office.id, role: :location, text: "Austin Office" }
337
326
  ]
338
327
  )
339
- jordan_austin_location.add_source(content: transfer_memo, type: :primary)
328
+ jordan_austin_location.add_source(source: transfer_memo, kind: :primary)
340
329
 
341
330
  puts "Recorded Jordan Taylor's transfer to Austin Office"
342
331
 
343
- # Section 6: Query Employee Information
344
- puts "\n--- Section 6: HR Queries ---\n"
332
+ demo_section("Section 6: Query Employee Information")
345
333
 
346
334
  # Current state of all employees
347
335
  puts "\nCurrent Employee Status:"
348
336
  puts "-" * 50
349
337
 
350
338
  employees.each do |key, employee|
351
- puts "\n#{employee.canonical_name}:"
339
+ puts "\n#{employee.name}:"
352
340
  current_facts = fact_service.current_facts(entity: employee.id)
353
341
  current_facts.each do |fact|
354
- puts " - #{fact.fact_text}"
342
+ puts " - #{fact.text}"
355
343
  end
356
344
  end
357
345
 
358
- # Section 7: Historical Query
359
- puts "\n--- Section 7: Historical Employee Query ---\n"
346
+ demo_section("Section 7: Historical Query")
360
347
 
361
348
  # What was Alex Kim's role in December 2024?
362
349
  puts "\nAlex Kim's facts as of December 2024:"
363
350
  past_facts = fact_service.facts_at(Date.new(2024, 12, 1), entity: employees[:junior_eng].id)
364
- past_facts.each { |f| puts " - #{f.fact_text}" }
351
+ past_facts.each { |f| puts " - #{f.text}" }
365
352
 
366
353
  # What is Alex Kim's role now?
367
354
  puts "\nAlex Kim's facts as of today:"
368
355
  current_facts = fact_service.facts_at(Date.today, entity: employees[:junior_eng].id)
369
- current_facts.each { |f| puts " - #{f.fact_text}" }
356
+ current_facts.each { |f| puts " - #{f.text}" }
370
357
 
371
- # Section 8: Organization Chart Query
372
- puts "\n--- Section 8: Organization Chart ---\n"
358
+ demo_section("Section 8: Organization Chart Query")
373
359
 
374
360
  puts "\nReporting relationships:"
375
361
  # Find all "reports to" facts
376
362
  reporting_facts = fact_service.search("reports to")
377
- reporting_facts.each { |f| puts " #{f.fact_text}" }
363
+ reporting_facts.each { |f| puts " #{f.text}" }
378
364
 
379
365
  puts "\nEngineering Department members:"
380
366
  engineering_facts = fact_service.current_facts(entity: engineering.id)
381
- engineering_facts.each { |f| puts " #{f.fact_text}" }
367
+ engineering_facts.each { |f| puts " #{f.text}" }
382
368
 
383
- # Section 9: Employee Timeline
384
- puts "\n--- Section 9: Marcus Chen Career Timeline ---\n"
369
+ demo_section("Section 9: Employee Timeline")
385
370
 
386
371
  timeline = fact_service.timeline(
387
372
  entity_id: employees[:vp_eng].id,
@@ -392,11 +377,10 @@ timeline = fact_service.timeline(
392
377
  timeline.each do |entry|
393
378
  end_date = entry[:invalid_at]&.strftime("%Y-%m-%d") || "present"
394
379
  status_marker = entry[:status] != "canonical" ? " [#{entry[:status]}]" : ""
395
- puts " #{entry[:valid_at].strftime('%Y-%m-%d')} - #{end_date}: #{entry[:fact_text]}#{status_marker}"
380
+ puts " #{entry[:valid_at].strftime('%Y-%m-%d')} - #{end_date}: #{entry[:text]}#{status_marker}"
396
381
  end
397
382
 
398
- # Section 10: Audit Trail
399
- puts "\n--- Section 10: Audit Trail for Alex Kim ---\n"
383
+ demo_section("Section 10: Audit Trail")
400
384
 
401
385
  alex_facts = FactDb::Models::Fact.joins(:entity_mentions)
402
386
  .where(entity_mentions: { entity_id: employees[:junior_eng].id })
@@ -406,23 +390,20 @@ puts "Complete fact history:"
406
390
  alex_facts.each do |fact|
407
391
  status_info = fact.status != "canonical" ? " [#{fact.status}]" : ""
408
392
  validity = fact.invalid_at ? "#{fact.valid_at} - #{fact.invalid_at}" : "#{fact.valid_at} - present"
409
- puts " [#{validity}] #{fact.fact_text}#{status_info}"
393
+ puts " [#{validity}] #{fact.text}#{status_info}"
410
394
 
411
395
  fact.fact_sources.each do |source|
412
- puts " Source: #{source.content.title} (#{source.source_type})"
396
+ puts " Source: #{source.source.title} (#{source.kind})"
413
397
  end
414
398
  end
415
399
 
416
- # Section 11: Statistics
417
- puts "\n--- Section 11: HR System Statistics ---\n"
400
+ demo_section("Section 11: Statistics")
418
401
 
419
- puts "Total employees tracked: #{entity_service.people.count}"
420
- puts "Total departments: #{entity_service.organizations.where("description LIKE ?", "%team%").count}"
402
+ puts "Total employees tracked: #{entity_service.by_kind("person").count}"
403
+ puts "Total departments: #{entity_service.by_kind("organization").where("description LIKE ?", "%team%").count}"
421
404
  puts "Total employment facts: #{fact_service.stats[:total]}"
422
405
  puts "Current facts: #{FactDb::Models::Fact.currently_valid.count}"
423
406
  puts "Historical facts: #{FactDb::Models::Fact.historical.count}"
424
- puts "Documents processed: #{content_service.stats[:total]}"
407
+ puts "Documents processed: #{source_service.stats[:total]}"
425
408
 
426
- puts "\n" + "=" * 60
427
- puts "HR System Demo Complete!"
428
- puts "=" * 60
409
+ demo_footer