Schema update

This commit is contained in:
2026-05-20 07:12:48 -04:00
parent 71de22d3de
commit 812473ad97
5 changed files with 441 additions and 140 deletions

View File

@@ -2,21 +2,24 @@
Neo4j Unified Knowledge Graph Schema Initialization
=====================================================
Creates the foundational schema for a unified knowledge graph used by
fourteen AI assistants across three teams:
sixteen AI assistants across three teams:
Personal Team:
Hypatia (Learning), Marcus (Fitness), Seneca (Reflection),
Nate (Travel), Bowie (Culture), Bourdain (Food),
Cousteau (Nature), Garth (Finance), Cristiano (Football)
Personal Team (Iolaus):
Shawn (General — calendar/contacts/comms), Nate (Travel),
Hypatia (Learning), Marcus (Fitness),
Watson (Reflection & Emotional Safety), Bourdain (Food),
David (Arts & Culture), Cousteau (Nature), Garth (Finance),
Cristiano (Football)
Work Team:
Alan (Strategy), Ann (Marketing), Jeffrey (Sales), Jarvis (Execution)
Work Team (Mentor):
Alan (Strategy), Ann (Marketing), Jeffrey (Sales),
Jarvis (Execution), AWS SA (Architecture)
Engineering Team:
Engineering Team (Kottos):
Scotty (Infrastructure), Harper (Prototyping)
Schema Reference:
docs/neo4j-unified-schema.md
docs/neo4j-unified-schema.md (v2.3.0)
Requirements:
pip install neo4j
@@ -72,7 +75,7 @@ class LifeGraphSchema:
"""
Create uniqueness constraints on key node properties.
This ensures data integrity and creates indexes automatically.
All 74 node types get an id uniqueness constraint.
All 79 node types get an id uniqueness constraint.
"""
constraints = [
# ── Universal nodes ──────────────────────────────────────
@@ -101,12 +104,16 @@ class LifeGraphSchema:
"CREATE CONSTRAINT personalrecord_id IF NOT EXISTS FOR (n:PersonalRecord) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT bodymetric_id IF NOT EXISTS FOR (n:BodyMetric) REQUIRE n.id IS UNIQUE",
# ── Seneca: Reflection & Wellness ────────────────────────
# ── Watson: Reflection & Emotional Safety ────────────────
"CREATE CONSTRAINT reflection_id IF NOT EXISTS FOR (n:Reflection) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT value_id IF NOT EXISTS FOR (n:Value) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT habit_id IF NOT EXISTS FOR (n:Habit) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT lifeevent_id IF NOT EXISTS FOR (n:LifeEvent) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT intention_id IF NOT EXISTS FOR (n:Intention) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT emotionalmemory_id IF NOT EXISTS FOR (n:EmotionalMemory) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT relationshiptheme_id IF NOT EXISTS FOR (n:RelationshipTheme) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT dialoguenote_id IF NOT EXISTS FOR (n:DialogueNote) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT dynamicpattern_id IF NOT EXISTS FOR (n:DynamicPattern) REQUIRE n.id IS UNIQUE",
# ── Bourdain: Food & Cooking ─────────────────────────────
"CREATE CONSTRAINT recipe_id IF NOT EXISTS FOR (n:Recipe) REQUIRE n.id IS UNIQUE",
@@ -115,7 +122,7 @@ class LifeGraphSchema:
"CREATE CONSTRAINT meal_id IF NOT EXISTS FOR (n:Meal) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT technique_id IF NOT EXISTS FOR (n:Technique) REQUIRE n.id IS UNIQUE",
# ── Bowie: Arts & Culture ────────────────────────────────
# ── David: Arts & Culture ────────────────────────────────
"CREATE CONSTRAINT music_id IF NOT EXISTS FOR (n:Music) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT film_id IF NOT EXISTS FOR (n:Film) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT artwork_id IF NOT EXISTS FOR (n:Artwork) REQUIRE n.id IS UNIQUE",
@@ -146,7 +153,10 @@ class LifeGraphSchema:
"CREATE CONSTRAINT tournament_id IF NOT EXISTS FOR (n:Tournament) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT player_id IF NOT EXISTS FOR (n:Player) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT season_id IF NOT EXISTS FOR (n:Season) REQUIRE n.id IS UNIQUE",
# ── Shawn: Personal General Assistant ────────────────────
"CREATE CONSTRAINT communication_id IF NOT EXISTS FOR (n:Communication) REQUIRE n.id IS UNIQUE",
# ── Work: Business ───────────────────────────────────────
"CREATE CONSTRAINT client_id IF NOT EXISTS FOR (n:Client) REQUIRE n.id IS UNIQUE",
"CREATE CONSTRAINT contact_id IF NOT EXISTS FOR (n:Contact) REQUIRE n.id IS UNIQUE",
@@ -258,6 +268,10 @@ class LifeGraphSchema:
"CREATE INDEX lifeevent_date IF NOT EXISTS FOR (n:LifeEvent) ON (n.date)",
"CREATE INDEX intention_date IF NOT EXISTS FOR (n:Intention) ON (n.date)",
"CREATE INDEX match_date IF NOT EXISTS FOR (n:Match) ON (n.date)",
"CREATE INDEX emotionalmemory_date IF NOT EXISTS FOR (n:EmotionalMemory) ON (n.date)",
"CREATE INDEX dialoguenote_date IF NOT EXISTS FOR (n:DialogueNote) ON (n.date)",
"CREATE INDEX dynamicpattern_date IF NOT EXISTS FOR (n:DynamicPattern) ON (n.date)",
"CREATE INDEX communication_date IF NOT EXISTS FOR (n:Communication) ON (n.date)",
# ── Type / Status / Category indexes ─────────────────────
"CREATE INDEX event_type IF NOT EXISTS FOR (n:Event) ON (n.type)",
@@ -304,6 +318,8 @@ class LifeGraphSchema:
"CREATE INDEX goal_domain IF NOT EXISTS FOR (n:Goal) ON (n.domain)",
"CREATE INDEX location_domain IF NOT EXISTS FOR (n:Location) ON (n.domain)",
"CREATE INDEX person_domain IF NOT EXISTS FOR (n:Person) ON (n.domain)",
"CREATE INDEX contact_domain IF NOT EXISTS FOR (n:Contact) ON (n.domain)",
"CREATE INDEX task_domain IF NOT EXISTS FOR (n:Task) ON (n.domain)",
]
with self.driver.session() as session:
@@ -426,16 +442,33 @@ class LifeGraphSchema:
"SHOW INDEXES WHERE name = 'client_status'",
lambda r: len(list(r)) == 1),
# Cristiano team sample
("Constraint: Match",
("Constraint: Match",
"SHOW CONSTRAINTS WHERE name = 'match_id'",
lambda r: len(list(r)) == 1),
("Constraint: Team",
("Constraint: Team",
"SHOW CONSTRAINTS WHERE name = 'team_id'",
lambda r: len(list(r)) == 1),
# Total constraint count (74 node types)
("Total constraints >= 74",
# Watson emotional-memory samples (v2.2.0)
("Constraint: EmotionalMemory",
"SHOW CONSTRAINTS WHERE name = 'emotionalmemory_id'",
lambda r: len(list(r)) == 1),
("Constraint: RelationshipTheme",
"SHOW CONSTRAINTS WHERE name = 'relationshiptheme_id'",
lambda r: len(list(r)) == 1),
("Constraint: DialogueNote",
"SHOW CONSTRAINTS WHERE name = 'dialoguenote_id'",
lambda r: len(list(r)) == 1),
("Constraint: DynamicPattern",
"SHOW CONSTRAINTS WHERE name = 'dynamicpattern_id'",
lambda r: len(list(r)) == 1),
# Shawn sample (v2.3.0)
("Constraint: Communication",
"SHOW CONSTRAINTS WHERE name = 'communication_id'",
lambda r: len(list(r)) == 1),
# Total constraint count (79 node types as of v2.3.0)
("Total constraints >= 79",
"SHOW CONSTRAINTS",
lambda r: len(list(r)) >= 74),
lambda r: len(list(r)) >= 79),
]
if include_schema_tests:
@@ -523,7 +556,7 @@ class LifeGraphSchema:
RETURN b.id AS id
"""),
# ── Personal: Sample goal (Seneca) ───────────────────────
# ── Personal: Sample goal (Watson) ───────────────────────
("Goal:goal_sample_2025", """
MERGE (g:Goal {id: 'goal_sample_2025'})
ON CREATE SET g.created_at = datetime()
@@ -556,6 +589,40 @@ class LifeGraphSchema:
RETURN a.id AS id
"""),
# ── Shawn: Sample personal contact + communication ───────
("Contact:contact_sample_personal", """
MERGE (c:Contact {id: 'contact_sample_personal'})
ON CREATE SET c.created_at = datetime()
SET c.name = 'Sample Personal Contact',
c.domain = 'personal',
c.relationship_strength = 'developing',
c.updated_at = datetime()
RETURN c.id AS id
"""),
("Communication:comm_sample", """
MERGE (c:Communication {id: 'comm_sample'})
ON CREATE SET c.created_at = datetime()
SET c.type = 'in_person',
c.contact_id = 'contact_sample_personal',
c.date = date(),
c.summary = 'Sample interaction record',
c.updated_at = datetime()
RETURN c.id AS id
"""),
# ── Watson: Sample emotional memory ──────────────────────
("EmotionalMemory:memory_sample", """
MERGE (m:EmotionalMemory {id: 'memory_sample'})
ON CREATE SET m.created_at = datetime()
SET m.date = date(),
m.theme = 'safety',
m.intensity = 3,
m.content = 'Sample emotional memory entry',
m.updated_at = datetime()
RETURN m.id AS id
"""),
# ── Work: Sample client ──────────────────────────────────
("Client:client_sample_corp", """
MERGE (c:Client {id: 'client_sample_corp'})
@@ -563,6 +630,7 @@ class LifeGraphSchema:
SET c.name = 'Sample Corp',
c.industry = 'Technology',
c.status = 'prospect',
c.domain = 'work',
c.updated_at = datetime()
RETURN c.id AS id
"""),
@@ -642,6 +710,8 @@ class LifeGraphSchema:
("PURSUING", "Person", "user_main", "Goal", "goal_sample_2025"),
("EXPLORES", "Book", "book_meditations_aurelius", "Topic", "topic_stoicism"),
("OWNS", "Person", "user_main", "Account", "account_tfsa_sample"),
("HAD", "Person", "user_main", "Communication", "comm_sample"),
("WITH", "Communication", "comm_sample", "Contact", "contact_sample_personal"),
]
created_rels = 0
@@ -688,31 +758,36 @@ Event Significant occurrences (celebrations, conferences)
Topic Subjects of interest (stoicism, AI in CX)
Goal Objectives (personal growth, career, fitness, financial)
PERSONAL TEAM:
PERSONAL TEAM (Iolaus):
────────────────────────────────────────────────────────────────
Shawn (General) Contact, Event, Task (all domain='personal'), Communication
Nate (Travel) Trip, Destination, Activity
Hypatia (Learning) Book, Author, LearningPath, Concept, Quote
Marcus (Fitness) Training, Exercise, Program, PersonalRecord, BodyMetric
Seneca (Reflection) Reflection, Value, Habit, LifeEvent, Intention
Watson (Reflection) Reflection, Value, Habit, LifeEvent, Intention,
EmotionalMemory, RelationshipTheme, DialogueNote, DynamicPattern
Bourdain (Food) Recipe, Restaurant, Ingredient, Meal, Technique
Bowie (Culture) Music, Film, Artwork, Playlist, Artist, Style
David (Culture) Music, Film, Artwork, Playlist, Artist, Style
Cousteau (Nature) Species, Plant, Tank, Garden, Ecosystem, Observation
Garth (Finance) Account, Investment, Asset, Liability, Budget, FinancialGoal
Cristiano (Football) Match, Team, League, Tournament, Player, Season
WORK TEAM:
WORK TEAM (Mentor):
────────────────────────────────────────────────────────────────
Alan (Strategy) Client, Vendor, Competitor, MarketTrend, Technology, Decision
Ann (Marketing) Content, Publication, Topic, Event
Jeffrey (Sales) Contact, Opportunity, Proposal, Meeting
Jarvis (Execution) Task, Meeting, Note, Decision, Project
Ann (Marketing) Content, Publication, Topic, Event (domain='work')
Jeffrey (Sales) Contact (domain='work'), Opportunity, Proposal, Meeting
Jarvis (Execution) Task (domain='work'), Meeting, Note, Decision, Project
AWS SA (Architecture) No domain ownership — writes Note (messages) only
ENGINEERING TEAM:
ENGINEERING TEAM (Kottos):
────────────────────────────────────────────────────────────────
Scotty (Infra) Infrastructure, Incident
Harper (Hacking) Prototype, Experiment
TOTAL: 74 node types, all with id uniqueness constraints
TOTAL: 79 node types, 16 assistants. All node types have id uniqueness
constraints. Contact/Event/Task are Universal with a `domain` field
('personal' or 'work') disambiguating Shawn vs. Jarvis/Jeffrey ownership.
CROSS-TEAM CONNECTIONS (examples):
────────────────────────────────────────────────────────────────
@@ -723,8 +798,9 @@ Infrastructure -[HOSTS]-> Project (Engineering ↔ Work)
Prototype -[SUPPORTS]-> Opportunity (Engineering ↔ Work)
Project -[GENERATES_REVENUE]-> Account (Work ↔ Personal)
Training -[BUILDS]-> Skill (Personal ↔ Work)
Communication -[WITH]-> Contact (Shawn: personal interaction history)
Full schema: docs/neo4j-unified-schema.md
Full schema: docs/neo4j-unified-schema.md (v2.3.0)
════════════════════════════════════════════════════════════════
"""
print(schema_doc)
@@ -858,7 +934,7 @@ def main():
schema.document_schema()
# Create constraints (includes automatic indexes)
logger.info("Creating constraints (74 node types)...")
logger.info("Creating constraints (79 node types)...")
schema.create_constraints()
# Create additional indexes
@@ -886,7 +962,7 @@ def main():
if test_success:
logger.info("✓ All tests passed!")
logger.info("\nUnified graph ready for all 15 assistants.")
logger.info("\nUnified graph ready for all 16 assistants.")
logger.info("Schema reference: docs/neo4j-unified-schema.md")
logger.info("\nNext steps:")
logger.info(" 1. Import data (Plex, Calibre, etc.)")

View File

@@ -35,11 +35,12 @@ EXPECTED_CONSTRAINTS = [
"book_id", "author_id", "learningpath_id", "concept_id", "quote_id",
# Marcus
"training_id", "exercise_id", "program_id", "personalrecord_id", "bodymetric_id",
# Seneca
# Watson (Reflection & Emotional Safety)
"reflection_id", "value_id", "habit_id", "lifeevent_id", "intention_id",
"emotionalmemory_id", "relationshiptheme_id", "dialoguenote_id", "dynamicpattern_id",
# Bourdain
"recipe_id", "restaurant_id", "ingredient_id", "meal_id", "technique_id",
# Bowie
# David (Arts & Culture)
"music_id", "film_id", "artwork_id", "playlist_id", "artist_id", "style_id",
# Cousteau
"species_id", "plant_id", "tank_id", "garden_id", "ecosystem_id", "observation_id",
@@ -47,6 +48,8 @@ EXPECTED_CONSTRAINTS = [
"account_id", "investment_id", "asset_id", "liability_id", "budget_id", "financialgoal_id",
# Cristiano
"match_id", "team_id", "league_id", "tournament_id", "player_id", "season_id",
# Shawn (Personal General Assistant)
"communication_id",
# Work: Business
"client_id", "contact_id", "opportunity_id", "proposal_id", "project_id",
# Work: Market Intelligence
@@ -68,11 +71,13 @@ EXPECTED_LABELS = {
"Book", "Author", "LearningPath", "Concept", "Quote",
"Training", "Exercise", "Program", "PersonalRecord", "BodyMetric",
"Reflection", "Value", "Habit", "LifeEvent", "Intention",
"EmotionalMemory", "RelationshipTheme", "DialogueNote", "DynamicPattern",
"Recipe", "Restaurant", "Ingredient", "Meal", "Technique",
"Music", "Film", "Artwork", "Playlist", "Artist", "Style",
"Species", "Plant", "Tank", "Garden", "Ecosystem", "Observation",
"Account", "Investment", "Asset", "Liability", "Budget", "FinancialGoal",
"Match", "Team", "League", "Tournament", "Player", "Season",
"Communication",
"Client", "Contact", "Opportunity", "Proposal", "Project",
"Vendor", "Competitor", "MarketTrend", "Technology",
"Content", "Publication",
@@ -90,6 +95,9 @@ EXPECTED_SAMPLE_NODES = [
("Topic", "topic_stoicism"),
("Topic", "topic_ai_in_cx"),
("Account", "account_tfsa_sample"),
("Contact", "contact_sample_personal"),
("Communication", "comm_sample"),
("EmotionalMemory", "memory_sample"),
("Client", "client_sample_corp"),
("Skill", "skill_cx_strategy"),
("Infrastructure", "infra_neo4j_prod"),
@@ -102,6 +110,8 @@ EXPECTED_SAMPLE_RELS = [
("Person", "user_main", "PURSUING", "Goal", "goal_sample_2025"),
("Book", "book_meditations_aurelius", "EXPLORES", "Topic", "topic_stoicism"),
("Person", "user_main", "OWNS", "Account", "account_tfsa_sample"),
("Person", "user_main", "HAD", "Communication", "comm_sample"),
("Communication", "comm_sample", "WITH", "Contact", "contact_sample_personal"),
]
# A sampling of expected indexes (not exhaustive, just key ones to spot-check)
@@ -109,6 +119,7 @@ EXPECTED_INDEX_SAMPLES = [
"person_name", "book_title", "client_name", "event_date",
"training_date", "client_status", "task_status", "event_domain",
"team_name", "player_name", "match_competition",
"contact_domain", "task_domain",
]
@@ -157,7 +168,7 @@ def validate(driver, uri):
print("" * 65)
print(" VALIDATION REPORT — Koios Unified Knowledge Graph")
print("" * 65)
print(f" Schema Version: 2.1.0")
print(f" Schema Version: 2.3.0")
print(f" Database: {uri}")
print(f" Timestamp: {now}")
print("" * 65)