#!/usr/bin/env python3 """Complete verification of all 7 success criteria for the Knowledge Graph.""" import sys import os import json os.chdir(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, '.') from graph import ( add_entity, add_relation, get_entity, search_entities, explore, merge_entities, decay_stale, infer_relations, health, init_db, _rebuild_index, DB_PATH ) import sqlite3 PASS = 0 FAIL = 0 def check(name, condition, detail=""): global PASS, FAIL if condition: PASS += 1 print(f" āœ… {name}") else: FAIL += 1 print(f" āŒ {name} — {detail}") # Clean slate init_db() conn = sqlite3.connect(DB_PATH) conn.execute("DELETE FROM relations") conn.execute("DELETE FROM entities") conn.commit() conn.close() _rebuild_index() print("=" * 60) print("KNOWLEDGE GRAPH — SUCCESS CRITERIA VERIFICATION") print("=" * 60) # === CRITERION 1: Health check === print("\nšŸ“‹ Criterion 1: Health endpoint returns 200") h = health() check("status is 'ok'", h["status"] == "ok") check("DB path exists", os.path.exists(h["database"]["path"])) check("embedding model reachable", h["embedding"]["reachable"]) # === CRITERION 2: Add entity and retrieve by ID === print("\nšŸ“‹ Criterion 2: Add entity and retrieve by ID") e1 = add_entity("person", "Alice Smith", "Alice is a software engineer living in Chicago.", aliases=["Alice"], tags=["engineer", "chicago"], confidence=0.95) entity = get_entity(e1) check("entity returned", entity is not None, f"got {entity}") check("correct subject", entity["subject"] == "Alice Smith") check("correct type", entity["type"] == "person") check("correct confidence", entity["confidence"] == 0.95) check("aliases stored", "Alice" in entity["aliases"]) check("tags stored", "chicago" in entity["tags"]) # === CRITERION 3: Add relation between two entities === print("\nšŸ“‹ Criterion 3: Add relation between two entities") e2 = add_entity("place", "Chicago", "Chicago, Illinois, USA.", tags=["city", "illinois"]) e3 = add_entity("person", "Bob Jones", "Bob is a designer in Chicago.", tags=["designer", "chicago"]) r1 = add_relation(e1, e2, "lives_in", confidence=1.0) r2 = add_relation(e2, e3, "located_in", confidence=0.9) entity1 = get_entity(e1) check("entity 1 has relations", len(entity1["relations_out"]) > 0) rel = entity1["relations_out"][0] check("relation type is lives_in", rel["relation_type"] == "lives_in") check("target is Chicago", rel["target_subject"] == "Chicago") entity3 = get_entity(e3) check("entity 3 has incoming relation", len(entity3["relations_in"]) > 0, f"got {len(entity3['relations_in'])}") check("incoming rel points to Chicago", entity3["relations_in"][0]["relation_type"] == "located_in") # === CRITERION 4: Semantic search === print("\nšŸ“‹ Criterion 4: Semantic search returns relevant results") results = search_entities("software developer in Illinois", top_k=3) check("search returned results", len(results) > 0, f"got {len(results)} results") check("Alice in top results", any(r["subject"] == "Alice Smith" for r in results)) results2 = search_entities("design professional", top_k=3) check("Bob in design search", any(r["subject"] == "Bob Jones" for r in results2)) # Search should order by relevance scores = [r["similarity"] for r in results] check("results sorted by similarity", all(scores[i] >= scores[i+1] for i in range(len(scores)-1))) # === CRITERION 5: Explore returns entity + 1-hop neighbors === print("\nšŸ“‹ Criterion 5: Explore returns entity + 1-hop neighbors") ex = explore(e1, depth=1) check("explore returns dict with root", "root" in ex) check("root is Alice Smith", ex["root"]["subject"] == "Alice Smith") check("nodes contains neighbors", len(ex["nodes"]) > 0) check("Chicago in neighbors", any(n["subject"] == "Chicago" for n in ex["nodes"].values())) check("edges found", len(ex["edges"]) > 0) # Depth 2 exploration e4 = add_entity("person", "Carol Chen", "Carol is Bob's colleague.", tags=["colleague"]) add_relation(e4, e3, "colleague_of", confidence=1.0) ex2 = explore(e2, depth=2) check("depth 2 finds Carol via Bob", any( n["subject"] == "Carol Chen" for n in ex2.get("nodes", {}).values() ) or any( "Carol" in str(e) for e in ex2.get("edges", []) )) # === CRITERION 6: Vague relation types rejected === print("\nšŸ“‹ Criterion 6: Vague relation types are rejected") vague_types = ["related_to", "associated_with", "connected_to", "linked_to", "has_relation", "involves", "correlates_with"] all_rejected = True for vt in vague_types: try: add_relation(e1, e2, vt) all_rejected = False print(f" āŒ '{vt}' was NOT rejected") except ValueError: pass check(f"all {len(vague_types)} vague types rejected", all_rejected) # Valid types should work valid_test = add_relation(e1, e4, "friend_of", confidence=0.5) check("valid type 'friend_of' accepted", valid_test is not None) # Invalid entity types rejected try: add_entity("robot", "TestBot", "A robot.") check("invalid entity type rejected", False) except ValueError: check("invalid entity type rejected", True) # === CRITERION 7: Duplicate subjects merged === print("\nšŸ“‹ Criterion 7: Duplicate subjects merged, not duplicated") e_dup = add_entity("person", "alice smith", "Same person, different case.", tags=["duplicate"]) check("dedup returns original ID", e_dup == e1, f"{e_dup} vs {e1}") # Verify merged data merged = get_entity(e1) check("tags merged", "duplicate" in merged["tags"], f"tags: {merged['tags']}") check("original tag preserved", "engineer" in merged["tags"]) check("different case in aliases", "alice smith" in merged["aliases"]) # Different entity types with same name should NOT merge e_diff_type = add_entity("project", "Alice Smith", "A project named after Alice.") check("different type, same name = separate entity", e_diff_type != e1, f"{e_diff_type} vs {e1}") check("project entity accessible", get_entity(e_diff_type) is not None) print() print("=" * 60) print(f"RESULTS: {PASS} passed, {FAIL} failed out of {PASS + FAIL} checks") print("=" * 60) sys.exit(0 if FAIL == 0 else 1)