fact_db 0.0.1
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.
- checksums.yaml +7 -0
- data/.envrc +1 -0
- data/CHANGELOG.md +48 -0
- data/COMMITS.md +196 -0
- data/README.md +102 -0
- data/Rakefile +41 -0
- data/db/migrate/001_enable_extensions.rb +7 -0
- data/db/migrate/002_create_contents.rb +44 -0
- data/db/migrate/003_create_entities.rb +36 -0
- data/db/migrate/004_create_entity_aliases.rb +18 -0
- data/db/migrate/005_create_facts.rb +65 -0
- data/db/migrate/006_create_entity_mentions.rb +18 -0
- data/db/migrate/007_create_fact_sources.rb +18 -0
- data/docs/api/extractors/index.md +71 -0
- data/docs/api/extractors/llm.md +162 -0
- data/docs/api/extractors/manual.md +92 -0
- data/docs/api/extractors/rule-based.md +165 -0
- data/docs/api/facts.md +300 -0
- data/docs/api/index.md +66 -0
- data/docs/api/models/content.md +165 -0
- data/docs/api/models/entity.md +202 -0
- data/docs/api/models/fact.md +270 -0
- data/docs/api/models/index.md +77 -0
- data/docs/api/pipeline/extraction.md +175 -0
- data/docs/api/pipeline/index.md +72 -0
- data/docs/api/pipeline/resolution.md +209 -0
- data/docs/api/services/content-service.md +166 -0
- data/docs/api/services/entity-service.md +202 -0
- data/docs/api/services/fact-service.md +223 -0
- data/docs/api/services/index.md +55 -0
- data/docs/architecture/database-schema.md +293 -0
- data/docs/architecture/entity-resolution.md +293 -0
- data/docs/architecture/index.md +149 -0
- data/docs/architecture/temporal-facts.md +268 -0
- data/docs/architecture/three-layer-model.md +242 -0
- data/docs/assets/css/custom.css +137 -0
- data/docs/assets/fact_db.jpg +0 -0
- data/docs/assets/images/fact_db.jpg +0 -0
- data/docs/concepts.md +183 -0
- data/docs/examples/basic-usage.md +235 -0
- data/docs/examples/hr-onboarding.md +312 -0
- data/docs/examples/index.md +64 -0
- data/docs/examples/news-analysis.md +288 -0
- data/docs/getting-started/database-setup.md +170 -0
- data/docs/getting-started/index.md +71 -0
- data/docs/getting-started/installation.md +98 -0
- data/docs/getting-started/quick-start.md +191 -0
- data/docs/guides/batch-processing.md +325 -0
- data/docs/guides/configuration.md +243 -0
- data/docs/guides/entity-management.md +364 -0
- data/docs/guides/extracting-facts.md +299 -0
- data/docs/guides/index.md +22 -0
- data/docs/guides/ingesting-content.md +252 -0
- data/docs/guides/llm-integration.md +299 -0
- data/docs/guides/temporal-queries.md +315 -0
- data/docs/index.md +121 -0
- data/examples/README.md +130 -0
- data/examples/basic_usage.rb +164 -0
- data/examples/entity_management.rb +216 -0
- data/examples/hr_system.rb +428 -0
- data/examples/rule_based_extraction.rb +258 -0
- data/examples/temporal_queries.rb +245 -0
- data/lib/fact_db/config.rb +71 -0
- data/lib/fact_db/database.rb +45 -0
- data/lib/fact_db/errors.rb +10 -0
- data/lib/fact_db/extractors/base.rb +117 -0
- data/lib/fact_db/extractors/llm_extractor.rb +179 -0
- data/lib/fact_db/extractors/manual_extractor.rb +53 -0
- data/lib/fact_db/extractors/rule_based_extractor.rb +228 -0
- data/lib/fact_db/llm/adapter.rb +109 -0
- data/lib/fact_db/models/content.rb +62 -0
- data/lib/fact_db/models/entity.rb +84 -0
- data/lib/fact_db/models/entity_alias.rb +26 -0
- data/lib/fact_db/models/entity_mention.rb +33 -0
- data/lib/fact_db/models/fact.rb +192 -0
- data/lib/fact_db/models/fact_source.rb +35 -0
- data/lib/fact_db/pipeline/extraction_pipeline.rb +146 -0
- data/lib/fact_db/pipeline/resolution_pipeline.rb +129 -0
- data/lib/fact_db/resolution/entity_resolver.rb +261 -0
- data/lib/fact_db/resolution/fact_resolver.rb +259 -0
- data/lib/fact_db/services/content_service.rb +93 -0
- data/lib/fact_db/services/entity_service.rb +150 -0
- data/lib/fact_db/services/fact_service.rb +193 -0
- data/lib/fact_db/temporal/query.rb +125 -0
- data/lib/fact_db/temporal/timeline.rb +134 -0
- data/lib/fact_db/version.rb +5 -0
- data/lib/fact_db.rb +141 -0
- data/mkdocs.yml +198 -0
- metadata +288 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# LLMExtractor
|
|
2
|
+
|
|
3
|
+
AI-powered fact extraction using large language models.
|
|
4
|
+
|
|
5
|
+
## Class: `FactDb::Extractors::LLMExtractor`
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
extractor = FactDb::Extractors::LLMExtractor.new(config)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- `ruby_llm` gem installed
|
|
14
|
+
- LLM provider configured (API key, model)
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
FactDb.configure do |config|
|
|
20
|
+
config.llm_provider = :openai
|
|
21
|
+
config.llm_model = "gpt-4o-mini"
|
|
22
|
+
config.llm_api_key = ENV['OPENAI_API_KEY']
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Methods
|
|
27
|
+
|
|
28
|
+
### extract
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
def extract(content)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Extract facts from content using LLM.
|
|
35
|
+
|
|
36
|
+
**Parameters:**
|
|
37
|
+
|
|
38
|
+
- `content` (Models::Content) - Content to process
|
|
39
|
+
|
|
40
|
+
**Returns:** `Array<Models::Fact>`
|
|
41
|
+
|
|
42
|
+
**Example:**
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
extractor = LLMExtractor.new(config)
|
|
46
|
+
facts = extractor.extract(content)
|
|
47
|
+
|
|
48
|
+
facts.each do |fact|
|
|
49
|
+
puts fact.fact_text
|
|
50
|
+
puts " Valid: #{fact.valid_at}"
|
|
51
|
+
puts " Confidence: #{fact.confidence}"
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Extraction Process
|
|
56
|
+
|
|
57
|
+
1. **Prompt Construction** - Build prompt with content text
|
|
58
|
+
2. **LLM Call** - Send to configured LLM provider
|
|
59
|
+
3. **Response Parsing** - Parse JSON response
|
|
60
|
+
4. **Fact Creation** - Create fact records
|
|
61
|
+
5. **Entity Resolution** - Resolve mentioned entities
|
|
62
|
+
6. **Source Linking** - Link facts to source content
|
|
63
|
+
|
|
64
|
+
## Prompt Structure
|
|
65
|
+
|
|
66
|
+
The extractor uses a structured prompt:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Extract temporal facts from this content. For each fact:
|
|
70
|
+
1. Identify the assertion (what is being stated)
|
|
71
|
+
2. Identify entities mentioned (people, organizations, places)
|
|
72
|
+
3. Determine when the fact became valid
|
|
73
|
+
4. Assess confidence level
|
|
74
|
+
|
|
75
|
+
Content:
|
|
76
|
+
{content.raw_text}
|
|
77
|
+
|
|
78
|
+
Return JSON:
|
|
79
|
+
{
|
|
80
|
+
"facts": [
|
|
81
|
+
{
|
|
82
|
+
"text": "...",
|
|
83
|
+
"valid_at": "YYYY-MM-DD",
|
|
84
|
+
"entities": [
|
|
85
|
+
{"name": "...", "type": "person|organization|place", "role": "subject|object|..."}
|
|
86
|
+
],
|
|
87
|
+
"confidence": 0.0-1.0
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Supported Providers
|
|
94
|
+
|
|
95
|
+
| Provider | Models | Config |
|
|
96
|
+
|----------|--------|--------|
|
|
97
|
+
| OpenAI | gpt-4o, gpt-4o-mini | `llm_provider: :openai` |
|
|
98
|
+
| Anthropic | claude-sonnet-4, claude-3-haiku | `llm_provider: :anthropic` |
|
|
99
|
+
| Google | gemini-2.0-flash | `llm_provider: :gemini` |
|
|
100
|
+
| Ollama | llama3.2, mistral | `llm_provider: :ollama` |
|
|
101
|
+
| AWS Bedrock | claude-sonnet-4 | `llm_provider: :bedrock` |
|
|
102
|
+
| OpenRouter | Various | `llm_provider: :openrouter` |
|
|
103
|
+
|
|
104
|
+
## Error Handling
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
begin
|
|
108
|
+
facts = extractor.extract(content)
|
|
109
|
+
rescue FactDb::ConfigurationError => e
|
|
110
|
+
# LLM not configured
|
|
111
|
+
puts "Config error: #{e.message}"
|
|
112
|
+
rescue FactDb::ExtractionError => e
|
|
113
|
+
# Extraction failed
|
|
114
|
+
puts "Extraction error: #{e.message}"
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Advantages
|
|
119
|
+
|
|
120
|
+
- Handles unstructured text
|
|
121
|
+
- Understands context and nuance
|
|
122
|
+
- Identifies implicit facts
|
|
123
|
+
- Resolves entities automatically
|
|
124
|
+
|
|
125
|
+
## Disadvantages
|
|
126
|
+
|
|
127
|
+
- API costs
|
|
128
|
+
- Latency
|
|
129
|
+
- Occasional errors
|
|
130
|
+
- Requires validation
|
|
131
|
+
|
|
132
|
+
## Best Practices
|
|
133
|
+
|
|
134
|
+
### 1. Validate Results
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
facts = extractor.extract(content)
|
|
138
|
+
facts.each do |fact|
|
|
139
|
+
if fact.confidence < 0.7
|
|
140
|
+
fact.update!(metadata: { needs_review: true })
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 2. Cache Responses
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
cache_key = "llm:#{content.content_hash}"
|
|
149
|
+
facts = Rails.cache.fetch(cache_key) do
|
|
150
|
+
extractor.extract(content)
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 3. Handle Rate Limits
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
require 'retryable'
|
|
158
|
+
|
|
159
|
+
Retryable.retryable(tries: 3, sleep: lambda { |n| 2**n }) do
|
|
160
|
+
extractor.extract(content)
|
|
161
|
+
end
|
|
162
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# ManualExtractor
|
|
2
|
+
|
|
3
|
+
API-driven fact creation for maximum control and accuracy.
|
|
4
|
+
|
|
5
|
+
## Class: `FactDb::Extractors::ManualExtractor`
|
|
6
|
+
|
|
7
|
+
The ManualExtractor doesn't automatically extract facts - instead it provides a structured interface for creating facts programmatically.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
extractor = FactDb::Extractors::ManualExtractor.new(config)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Methods
|
|
16
|
+
|
|
17
|
+
### extract
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
def extract(content)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Returns an empty array - manual extraction is done via direct fact creation.
|
|
24
|
+
|
|
25
|
+
**Returns:** `[]`
|
|
26
|
+
|
|
27
|
+
## When to Use
|
|
28
|
+
|
|
29
|
+
- High-stakes facts that require human verification
|
|
30
|
+
- Structured data import from external systems
|
|
31
|
+
- Fact correction or adjustment
|
|
32
|
+
- Initial seeding of the system
|
|
33
|
+
|
|
34
|
+
## Creating Facts Manually
|
|
35
|
+
|
|
36
|
+
Instead of using the extractor, create facts directly:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
facts = FactDb.new
|
|
40
|
+
|
|
41
|
+
# Create entities
|
|
42
|
+
paula = facts.entity_service.create("Paula Chen", type: :person)
|
|
43
|
+
microsoft = facts.entity_service.create("Microsoft", type: :organization)
|
|
44
|
+
|
|
45
|
+
# Create fact with explicit links
|
|
46
|
+
fact = facts.fact_service.create(
|
|
47
|
+
"Paula Chen joined Microsoft as Principal Engineer",
|
|
48
|
+
valid_at: Date.parse("2024-01-10"),
|
|
49
|
+
mentions: [
|
|
50
|
+
{ entity: paula, role: "subject", text: "Paula Chen" },
|
|
51
|
+
{ entity: microsoft, role: "organization", text: "Microsoft" }
|
|
52
|
+
],
|
|
53
|
+
sources: [
|
|
54
|
+
{ content: announcement, type: "primary", excerpt: "...accepted the offer..." }
|
|
55
|
+
],
|
|
56
|
+
confidence: 1.0
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Bulk Import Pattern
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# Import from structured data
|
|
64
|
+
data = [
|
|
65
|
+
{ text: "Fact 1", date: "2024-01-01", entity: "Paula" },
|
|
66
|
+
{ text: "Fact 2", date: "2024-01-15", entity: "Paula" }
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
data.each do |item|
|
|
70
|
+
entity = facts.resolve_entity(item[:entity])
|
|
71
|
+
|
|
72
|
+
facts.fact_service.create(
|
|
73
|
+
item[:text],
|
|
74
|
+
valid_at: Date.parse(item[:date]),
|
|
75
|
+
mentions: [{ entity: entity, role: "subject", text: item[:entity] }],
|
|
76
|
+
extraction_method: "manual"
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Advantages
|
|
82
|
+
|
|
83
|
+
- Complete control over fact creation
|
|
84
|
+
- Highest accuracy (human-verified)
|
|
85
|
+
- No LLM costs
|
|
86
|
+
- Works without external dependencies
|
|
87
|
+
|
|
88
|
+
## Disadvantages
|
|
89
|
+
|
|
90
|
+
- Labor intensive
|
|
91
|
+
- Not scalable for large volumes
|
|
92
|
+
- Requires domain expertise
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# RuleBasedExtractor
|
|
2
|
+
|
|
3
|
+
Pattern-based fact extraction using regular expressions.
|
|
4
|
+
|
|
5
|
+
## Class: `FactDb::Extractors::RuleBasedExtractor`
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
extractor = FactDb::Extractors::RuleBasedExtractor.new(config)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Methods
|
|
12
|
+
|
|
13
|
+
### extract
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
def extract(content)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Extract facts using pattern matching.
|
|
20
|
+
|
|
21
|
+
**Returns:** `Array<Models::Fact>`
|
|
22
|
+
|
|
23
|
+
## Built-in Patterns
|
|
24
|
+
|
|
25
|
+
The extractor includes patterns for common fact types:
|
|
26
|
+
|
|
27
|
+
### Employment Events
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
# "X joined Y"
|
|
31
|
+
/(?<person>\w+(?:\s+\w+)*)\s+joined\s+(?<org>\w+(?:\s+\w+)*)/i
|
|
32
|
+
|
|
33
|
+
# "X left Y"
|
|
34
|
+
/(?<person>\w+(?:\s+\w+)*)\s+left\s+(?<org>\w+(?:\s+\w+)*)/i
|
|
35
|
+
|
|
36
|
+
# "X was hired by Y"
|
|
37
|
+
/(?<person>\w+(?:\s+\w+)*)\s+was\s+hired\s+by\s+(?<org>\w+(?:\s+\w+)*)/i
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Title Changes
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
# "X is/was the Y"
|
|
44
|
+
/(?<person>\w+(?:\s+\w+)*)\s+(?:is|was)\s+(?:the\s+)?(?<title>[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/
|
|
45
|
+
|
|
46
|
+
# "X promoted to Y"
|
|
47
|
+
/(?<person>\w+(?:\s+\w+)*)\s+(?:was\s+)?promoted\s+to\s+(?<title>[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/i
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Date Patterns
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# "on January 10, 2024"
|
|
54
|
+
/on\s+(?<month>\w+)\s+(?<day>\d{1,2}),?\s+(?<year>\d{4})/i
|
|
55
|
+
|
|
56
|
+
# "in Q4 2024"
|
|
57
|
+
/in\s+Q(?<quarter>\d)\s+(?<year>\d{4})/i
|
|
58
|
+
|
|
59
|
+
# ISO dates
|
|
60
|
+
/(?<date>\d{4}-\d{2}-\d{2})/
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Usage Example
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
extractor = RuleBasedExtractor.new(config)
|
|
67
|
+
|
|
68
|
+
content = Models::Content.create!(
|
|
69
|
+
raw_text: "Paula Chen joined Microsoft on January 10, 2024 as Principal Engineer.",
|
|
70
|
+
content_type: "announcement",
|
|
71
|
+
captured_at: Time.current
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
facts = extractor.extract(content)
|
|
75
|
+
# Returns facts about:
|
|
76
|
+
# - Paula joining Microsoft
|
|
77
|
+
# - Paula's title as Principal Engineer
|
|
78
|
+
# - Date: January 10, 2024
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Adding Custom Patterns
|
|
82
|
+
|
|
83
|
+
Extend the extractor with custom patterns:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
class CustomRuleExtractor < FactDb::Extractors::RuleBasedExtractor
|
|
87
|
+
CUSTOM_PATTERNS = [
|
|
88
|
+
{
|
|
89
|
+
pattern: /revenue\s+of\s+\$(?<amount>[\d,]+)/i,
|
|
90
|
+
type: :financial,
|
|
91
|
+
handler: :extract_revenue
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
def extract(content)
|
|
96
|
+
facts = super(content)
|
|
97
|
+
facts + extract_custom_patterns(content)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def extract_custom_patterns(content)
|
|
103
|
+
facts = []
|
|
104
|
+
CUSTOM_PATTERNS.each do |rule|
|
|
105
|
+
content.raw_text.scan(rule[:pattern]) do |match|
|
|
106
|
+
facts << send(rule[:handler], match, content)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
facts
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def extract_revenue(match, content)
|
|
113
|
+
Models::Fact.create!(
|
|
114
|
+
fact_text: "Revenue of $#{match[:amount]}",
|
|
115
|
+
valid_at: content.captured_at,
|
|
116
|
+
extraction_method: "rule_based",
|
|
117
|
+
# ...
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Advantages
|
|
124
|
+
|
|
125
|
+
- Fast execution
|
|
126
|
+
- No external dependencies
|
|
127
|
+
- Predictable results
|
|
128
|
+
- Works offline
|
|
129
|
+
- Zero cost
|
|
130
|
+
|
|
131
|
+
## Disadvantages
|
|
132
|
+
|
|
133
|
+
- Limited to defined patterns
|
|
134
|
+
- Misses implicit facts
|
|
135
|
+
- Requires pattern maintenance
|
|
136
|
+
- May produce false positives
|
|
137
|
+
|
|
138
|
+
## Best Practices
|
|
139
|
+
|
|
140
|
+
### 1. Combine with LLM
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
# Use rule-based for structured content
|
|
144
|
+
if content.content_type == "form"
|
|
145
|
+
facts = rule_extractor.extract(content)
|
|
146
|
+
else
|
|
147
|
+
facts = llm_extractor.extract(content)
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 2. Validate Matches
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
facts = extractor.extract(content)
|
|
155
|
+
facts.select { |f| f.confidence > 0.8 }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 3. Log Unmatched Content
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
facts = extractor.extract(content)
|
|
162
|
+
if facts.empty?
|
|
163
|
+
logger.info "No patterns matched for content #{content.id}"
|
|
164
|
+
end
|
|
165
|
+
```
|
data/docs/api/facts.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Facts
|
|
2
|
+
|
|
3
|
+
The main interface for FactDb operations.
|
|
4
|
+
|
|
5
|
+
## Class: `FactDb::Facts`
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
facts = FactDb.new
|
|
9
|
+
# or
|
|
10
|
+
facts = FactDb::Facts.new(config: custom_config)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Attributes
|
|
14
|
+
|
|
15
|
+
| Attribute | Type | Description |
|
|
16
|
+
|-----------|------|-------------|
|
|
17
|
+
| `config` | Config | Configuration instance |
|
|
18
|
+
| `content_service` | ContentService | Service for content operations |
|
|
19
|
+
| `entity_service` | EntityService | Service for entity operations |
|
|
20
|
+
| `fact_service` | FactService | Service for fact operations |
|
|
21
|
+
| `extraction_pipeline` | ExtractionPipeline | Pipeline for batch extraction |
|
|
22
|
+
| `resolution_pipeline` | ResolutionPipeline | Pipeline for batch resolution |
|
|
23
|
+
|
|
24
|
+
## Methods
|
|
25
|
+
|
|
26
|
+
### initialize
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
def initialize(config: nil)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Create a new Facts instance.
|
|
33
|
+
|
|
34
|
+
**Parameters:**
|
|
35
|
+
|
|
36
|
+
- `config` (Config, optional) - Configuration instance. Uses `FactDb.config` if not provided.
|
|
37
|
+
|
|
38
|
+
**Example:**
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# Use default configuration
|
|
42
|
+
facts = FactDb.new
|
|
43
|
+
|
|
44
|
+
# Use custom configuration
|
|
45
|
+
config = FactDb::Config.new
|
|
46
|
+
config.database_url = "postgresql://localhost/my_db"
|
|
47
|
+
facts = FactDb.new(config: config)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### ingest
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
def ingest(raw_text, type:, captured_at: Time.current, metadata: {}, title: nil, source_uri: nil)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Ingest raw content into the fact database.
|
|
59
|
+
|
|
60
|
+
**Parameters:**
|
|
61
|
+
|
|
62
|
+
- `raw_text` (String) - The content text
|
|
63
|
+
- `type` (Symbol) - Content type (:email, :document, :article, etc.)
|
|
64
|
+
- `captured_at` (Time, optional) - When content was captured
|
|
65
|
+
- `metadata` (Hash, optional) - Additional metadata
|
|
66
|
+
- `title` (String, optional) - Content title
|
|
67
|
+
- `source_uri` (String, optional) - Original location
|
|
68
|
+
|
|
69
|
+
**Returns:** `Models::Content`
|
|
70
|
+
|
|
71
|
+
**Example:**
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
content = facts.ingest(
|
|
75
|
+
"Paula joined Microsoft on Jan 10, 2024",
|
|
76
|
+
type: :announcement,
|
|
77
|
+
title: "New Hire",
|
|
78
|
+
captured_at: Time.current
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### extract_facts
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
def extract_facts(content_id, extractor: @config.default_extractor)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Extract facts from content.
|
|
91
|
+
|
|
92
|
+
**Parameters:**
|
|
93
|
+
|
|
94
|
+
- `content_id` (Integer) - Content ID
|
|
95
|
+
- `extractor` (Symbol, optional) - Extraction method (:manual, :llm, :rule_based)
|
|
96
|
+
|
|
97
|
+
**Returns:** `Array<Models::Fact>`
|
|
98
|
+
|
|
99
|
+
**Example:**
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
extracted = facts.extract_facts(content.id, extractor: :llm)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### query_facts
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
def query_facts(topic: nil, at: nil, entity: nil, status: :canonical)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Query facts with temporal and entity filtering.
|
|
114
|
+
|
|
115
|
+
**Parameters:**
|
|
116
|
+
|
|
117
|
+
- `topic` (String, optional) - Text search query
|
|
118
|
+
- `at` (Date/Time, optional) - Point in time (nil = current)
|
|
119
|
+
- `entity` (Integer, optional) - Entity ID filter
|
|
120
|
+
- `status` (Symbol, optional) - Fact status filter
|
|
121
|
+
|
|
122
|
+
**Returns:** `ActiveRecord::Relation<Models::Fact>`
|
|
123
|
+
|
|
124
|
+
**Example:**
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# Current facts about Paula
|
|
128
|
+
results = facts.query_facts(entity: paula.id)
|
|
129
|
+
|
|
130
|
+
# Facts on a topic
|
|
131
|
+
results = facts.query_facts(topic: "engineering")
|
|
132
|
+
|
|
133
|
+
# Historical query
|
|
134
|
+
results = facts.query_facts(at: Date.parse("2023-06-15"))
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### resolve_entity
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
def resolve_entity(name, type: nil)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Resolve a name to an entity.
|
|
146
|
+
|
|
147
|
+
**Parameters:**
|
|
148
|
+
|
|
149
|
+
- `name` (String) - Name to resolve
|
|
150
|
+
- `type` (Symbol, optional) - Entity type filter
|
|
151
|
+
|
|
152
|
+
**Returns:** `Models::Entity` or `nil`
|
|
153
|
+
|
|
154
|
+
**Example:**
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
entity = facts.resolve_entity("Paula Chen", type: :person)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### timeline_for
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
def timeline_for(entity_id, from: nil, to: nil)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Build a timeline for an entity.
|
|
169
|
+
|
|
170
|
+
**Parameters:**
|
|
171
|
+
|
|
172
|
+
- `entity_id` (Integer) - Entity ID
|
|
173
|
+
- `from` (Date/Time, optional) - Start of range
|
|
174
|
+
- `to` (Date/Time, optional) - End of range
|
|
175
|
+
|
|
176
|
+
**Returns:** `Array<Models::Fact>`
|
|
177
|
+
|
|
178
|
+
**Example:**
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
timeline = facts.timeline_for(paula.id, from: "2023-01-01", to: "2024-12-31")
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### current_facts_for
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
def current_facts_for(entity_id)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Get currently valid facts about an entity.
|
|
193
|
+
|
|
194
|
+
**Parameters:**
|
|
195
|
+
|
|
196
|
+
- `entity_id` (Integer) - Entity ID
|
|
197
|
+
|
|
198
|
+
**Returns:** `ActiveRecord::Relation<Models::Fact>`
|
|
199
|
+
|
|
200
|
+
**Example:**
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
current = facts.current_facts_for(paula.id)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### facts_at
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
def facts_at(at, entity: nil, topic: nil)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Get facts valid at a specific point in time.
|
|
215
|
+
|
|
216
|
+
**Parameters:**
|
|
217
|
+
|
|
218
|
+
- `at` (Date/Time) - Point in time
|
|
219
|
+
- `entity` (Integer, optional) - Entity ID filter
|
|
220
|
+
- `topic` (String, optional) - Text search query
|
|
221
|
+
|
|
222
|
+
**Returns:** `ActiveRecord::Relation<Models::Fact>`
|
|
223
|
+
|
|
224
|
+
**Example:**
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
historical = facts.facts_at(Date.parse("2023-06-15"), entity: paula.id)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
### batch_extract
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
def batch_extract(content_ids, extractor: @config.default_extractor, parallel: true)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Batch extract facts from multiple content items.
|
|
239
|
+
|
|
240
|
+
**Parameters:**
|
|
241
|
+
|
|
242
|
+
- `content_ids` (Array<Integer>) - Content IDs to process
|
|
243
|
+
- `extractor` (Symbol, optional) - Extraction method
|
|
244
|
+
- `parallel` (Boolean, optional) - Use parallel processing (default: true)
|
|
245
|
+
|
|
246
|
+
**Returns:** `Array<Hash>` - Results per content
|
|
247
|
+
|
|
248
|
+
**Example:**
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
results = facts.batch_extract([c1.id, c2.id, c3.id], parallel: true)
|
|
252
|
+
results.each do |r|
|
|
253
|
+
puts "#{r[:content_id]}: #{r[:facts].count} facts"
|
|
254
|
+
end
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### batch_resolve_entities
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
def batch_resolve_entities(names, type: nil)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Batch resolve entity names.
|
|
266
|
+
|
|
267
|
+
**Parameters:**
|
|
268
|
+
|
|
269
|
+
- `names` (Array<String>) - Names to resolve
|
|
270
|
+
- `type` (Symbol, optional) - Entity type filter
|
|
271
|
+
|
|
272
|
+
**Returns:** `Array<Hash>` - Resolution results
|
|
273
|
+
|
|
274
|
+
**Example:**
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
results = facts.batch_resolve_entities(["Paula", "Microsoft"])
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### detect_fact_conflicts
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
def detect_fact_conflicts(entity_ids)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Detect fact conflicts for multiple entities.
|
|
289
|
+
|
|
290
|
+
**Parameters:**
|
|
291
|
+
|
|
292
|
+
- `entity_ids` (Array<Integer>) - Entity IDs to check
|
|
293
|
+
|
|
294
|
+
**Returns:** `Array<Hash>` - Conflict detection results
|
|
295
|
+
|
|
296
|
+
**Example:**
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
conflicts = facts.detect_fact_conflicts([paula.id, john.id])
|
|
300
|
+
```
|