HR Onboarding Example¶
Track employee lifecycle events - hiring, promotions, transfers, and departures.
Scenario¶
An HR system that tracks employee facts over time, maintaining a complete audit trail of employment events.
Setup¶
require 'fact_db'
FactDb.configure do |config|
config.database.url = ENV['DATABASE_URL']
config.llm.provider = :openai
config.llm.api_key = ENV['OPENAI_API_KEY']
end
facts = FactDb.new
Create Organization Structure¶
# Company
acme = facts.entity_service.create(
"Acme Corporation",
type: :organization,
aliases: ["Acme", "Acme Corp"]
)
# Departments
engineering = facts.entity_service.create(
"Engineering Department",
type: :organization,
aliases: ["Engineering", "Eng"]
)
sales = facts.entity_service.create(
"Sales Department",
type: :organization,
aliases: ["Sales"]
)
# Locations
hq = facts.entity_service.create(
"Headquarters",
type: :place,
aliases: ["HQ", "Main Office"],
metadata: { address: "123 Main St, San Francisco, CA" }
)
Track Hiring Event¶
# Ingest offer letter
offer_letter = facts.ingest(
<<~TEXT,
Dear Paula Chen,
We are pleased to offer you the position of Software Engineer
at Acme Corporation, starting March 1, 2022.
Your starting salary will be $120,000 per year.
You will report to John Smith, Engineering Manager.
Location: Headquarters, San Francisco
TEXT
type: :document,
title: "Offer Letter - Paula Chen",
captured_at: Date.parse("2022-02-15")
)
# Create employee
paula = facts.entity_service.create(
"Paula Chen",
type: :person,
aliases: ["Paula"],
metadata: { employee_id: "E001" }
)
john = facts.entity_service.create(
"John Smith",
type: :person,
aliases: ["John"],
metadata: { employee_id: "M001" }
)
# Create employment facts
facts.fact_service.create(
"Paula Chen is employed at Acme Corporation",
valid_at: Date.parse("2022-03-01"),
mentions: [
{ entity: paula, role: "subject", text: "Paula Chen" },
{ entity: acme, role: "organization", text: "Acme Corporation" }
],
sources: [{ source: offer_letter, type: "primary" }]
)
facts.fact_service.create(
"Paula Chen's title is Software Engineer",
valid_at: Date.parse("2022-03-01"),
mentions: [{ entity: paula, role: "subject", text: "Paula Chen" }],
sources: [{ source: offer_letter, type: "primary" }]
)
facts.fact_service.create(
"Paula Chen reports to John Smith",
valid_at: Date.parse("2022-03-01"),
mentions: [
{ entity: paula, role: "subject", text: "Paula Chen" },
{ entity: john, role: "object", text: "John Smith" }
],
sources: [{ source: offer_letter, type: "primary" }]
)
facts.fact_service.create(
"Paula Chen works in Engineering Department",
valid_at: Date.parse("2022-03-01"),
mentions: [
{ entity: paula, role: "subject", text: "Paula Chen" },
{ entity: engineering, role: "organization", text: "Engineering" }
],
sources: [{ source: offer_letter, type: "primary" }]
)
Track Promotion¶
# Ingest promotion letter
promotion = facts.ingest(
<<~TEXT,
Dear Paula,
Congratulations! Effective January 15, 2023, you have been
promoted to Senior Software Engineer.
Your new salary will be $145,000 per year.
TEXT
type: :document,
title: "Promotion Letter - Paula Chen",
captured_at: Date.parse("2023-01-10")
)
# Supersede title fact
title_fact = FactDb::Models::Fact
.mentioning_entity(paula.id)
.search_text("title")
.canonical
.first
facts.fact_service.resolver.supersede(
title_fact.id,
"Paula Chen's title is Senior Software Engineer",
valid_at: Date.parse("2023-01-15")
)
Track Transfer¶
# Ingest transfer notice
transfer = facts.ingest(
<<~TEXT,
Effective July 1, 2023, Paula Chen will transfer from
Engineering to Sales as Sales Engineer, reporting to
Maria Garcia.
TEXT
type: :document,
title: "Transfer Notice - Paula Chen",
captured_at: Date.parse("2023-06-15")
)
maria = facts.entity_service.create(
"Maria Garcia",
type: :person,
metadata: { employee_id: "M002" }
)
# Supersede department fact
dept_fact = FactDb::Models::Fact
.mentioning_entity(paula.id)
.search_text("Department")
.canonical
.first
facts.fact_service.resolver.supersede(
dept_fact.id,
"Paula Chen works in Sales Department",
valid_at: Date.parse("2023-07-01")
)
# Supersede manager fact
manager_fact = FactDb::Models::Fact
.mentioning_entity(paula.id)
.search_text("reports to")
.canonical
.first
facts.fact_service.resolver.supersede(
manager_fact.id,
"Paula Chen reports to Maria Garcia",
valid_at: Date.parse("2023-07-01")
)
# Supersede title
title_fact = FactDb::Models::Fact
.mentioning_entity(paula.id)
.search_text("title")
.canonical
.first
facts.fact_service.resolver.supersede(
title_fact.id,
"Paula Chen's title is Sales Engineer",
valid_at: Date.parse("2023-07-01")
)
Query Employment History¶
# Complete timeline
puts "Paula Chen's Employment Timeline:"
puts "=" * 50
facts.timeline_for(paula.id).each do |fact|
valid = fact.invalid_at ?
"#{fact.valid_at.to_date} - #{fact.invalid_at.to_date}" :
"#{fact.valid_at.to_date} - present"
status = fact.superseded? ? " [superseded]" : ""
puts "#{valid}: #{fact.text}#{status}"
end
Output:
Paula Chen's Employment Timeline:
==================================================
2022-03-01 - present: Paula Chen is employed at Acme Corporation
2022-03-01 - 2023-01-15: Paula Chen's title is Software Engineer [superseded]
2023-01-15 - 2023-07-01: Paula Chen's title is Senior Software Engineer [superseded]
2023-07-01 - present: Paula Chen's title is Sales Engineer
2022-03-01 - 2023-07-01: Paula Chen works in Engineering Department [superseded]
2023-07-01 - present: Paula Chen works in Sales Department
2022-03-01 - 2023-07-01: Paula Chen reports to John Smith [superseded]
2023-07-01 - present: Paula Chen reports to Maria Garcia
Point-in-Time Queries¶
# What was Paula's status on different dates?
dates = [
Date.parse("2022-06-01"),
Date.parse("2023-03-01"),
Date.parse("2023-10-01")
]
dates.each do |date|
puts "\nPaula's status on #{date}:"
facts.facts_at(date, entity: paula.id).each do |fact|
puts " - #{fact.text}"
end
end
Generate Employment Report¶
def employment_report(facts, employee_id)
employee = FactDb::Models::Entity.find(employee_id)
current = facts.current_facts_for(employee_id)
report = {
name: employee.name,
current_status: {},
history: []
}
# Current status
current.each do |fact|
if fact.text.include?("title is")
report[:current_status][:title] = fact.text.split("title is ").last
elsif fact.text.include?("works in")
report[:current_status][:department] = fact.text.split("works in ").last
elsif fact.text.include?("reports to")
report[:current_status][:manager] = fact.text.split("reports to ").last
end
end
# Employment history
report[:history] = facts.timeline_for(employee_id).map do |fact|
{
fact: fact.text,
from: fact.valid_at,
to: fact.invalid_at,
status: fact.status
}
end
report
end
report = employment_report(facts, paula.id)
puts JSON.pretty_generate(report)