mirror of
https://github.com/langchain-ai/langsmith-agent-lifecycle-workshop.git
synced 2026-08-26 18:16:45 -04:00
473 lines
14 KiB
Python
473 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Validate TechHub SQLite database.
|
|
|
|
Comprehensive validation checks from full_project_plan.md lines 1428-1486:
|
|
- Record counts
|
|
- Foreign key integrity
|
|
- Date logic
|
|
- Status distributions
|
|
- Order totals
|
|
- Query performance
|
|
"""
|
|
|
|
import sqlite3
|
|
import time
|
|
from pathlib import Path
|
|
|
|
# Configuration
|
|
DATA_DIR = Path(__file__).parent.parent / "data" / "structured"
|
|
DB_PATH = DATA_DIR / "techhub.db"
|
|
|
|
|
|
def connect_database():
|
|
"""Connect to database with foreign keys enabled."""
|
|
if not DB_PATH.exists():
|
|
raise FileNotFoundError(f"Database not found: {DB_PATH}")
|
|
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.execute("PRAGMA foreign_keys = ON")
|
|
return conn
|
|
|
|
|
|
def validate_record_counts(cursor):
|
|
"""Validate record counts match expectations."""
|
|
print("\n" + "=" * 60)
|
|
print("RECORD COUNT VALIDATION")
|
|
print("=" * 60)
|
|
|
|
expected = {
|
|
"customers": 50,
|
|
"products": 25,
|
|
"orders": 250,
|
|
"order_items": (420, 600), # Range
|
|
}
|
|
|
|
for table, expected_count in expected.items():
|
|
cursor.execute(f"SELECT COUNT(*) FROM {table}")
|
|
actual = cursor.fetchone()[0]
|
|
|
|
if isinstance(expected_count, tuple):
|
|
min_count, max_count = expected_count
|
|
status = "✓" if min_count <= actual <= max_count else "✗"
|
|
print(f"{status} {table}: {actual} (expected: {min_count}-{max_count})")
|
|
assert min_count <= actual <= max_count, f"{table} count out of range"
|
|
else:
|
|
status = "✓" if actual == expected_count else "✗"
|
|
print(f"{status} {table}: {actual} (expected: {expected_count})")
|
|
assert actual == expected_count, f"{table} count mismatch"
|
|
|
|
|
|
def validate_foreign_keys(cursor):
|
|
"""Validate no orphaned records."""
|
|
print("\n" + "=" * 60)
|
|
print("FOREIGN KEY INTEGRITY")
|
|
print("=" * 60)
|
|
|
|
# Check orders reference valid customers
|
|
cursor.execute(
|
|
"""
|
|
SELECT COUNT(*)
|
|
FROM orders
|
|
WHERE customer_id NOT IN (SELECT customer_id FROM customers)
|
|
"""
|
|
)
|
|
orphaned_orders = cursor.fetchone()[0]
|
|
print(
|
|
f"{'✓' if orphaned_orders == 0 else '✗'} Orders with invalid customer_id: {orphaned_orders}"
|
|
)
|
|
assert orphaned_orders == 0, "Found orphaned orders"
|
|
|
|
# Check order_items reference valid orders
|
|
cursor.execute(
|
|
"""
|
|
SELECT COUNT(*)
|
|
FROM order_items
|
|
WHERE order_id NOT IN (SELECT order_id FROM orders)
|
|
"""
|
|
)
|
|
orphaned_items_orders = cursor.fetchone()[0]
|
|
print(
|
|
f"{'✓' if orphaned_items_orders == 0 else '✗'} Order items with invalid order_id: {orphaned_items_orders}"
|
|
)
|
|
assert orphaned_items_orders == 0, "Found order items with invalid order_id"
|
|
|
|
# Check order_items reference valid products
|
|
cursor.execute(
|
|
"""
|
|
SELECT COUNT(*)
|
|
FROM order_items
|
|
WHERE product_id NOT IN (SELECT product_id FROM products)
|
|
"""
|
|
)
|
|
orphaned_items_products = cursor.fetchone()[0]
|
|
print(
|
|
f"{'✓' if orphaned_items_products == 0 else '✗'} Order items with invalid product_id: {orphaned_items_products}"
|
|
)
|
|
assert orphaned_items_products == 0, "Found order items with invalid product_id"
|
|
|
|
|
|
def validate_date_logic(cursor):
|
|
"""Validate date relationships."""
|
|
print("\n" + "=" * 60)
|
|
print("DATE LOGIC VALIDATION")
|
|
print("=" * 60)
|
|
|
|
# Check shipped_date >= order_date
|
|
cursor.execute(
|
|
"""
|
|
SELECT COUNT(*)
|
|
FROM orders
|
|
WHERE shipped_date IS NOT NULL
|
|
AND shipped_date < order_date
|
|
"""
|
|
)
|
|
invalid_dates = cursor.fetchone()[0]
|
|
print(
|
|
f"{'✓' if invalid_dates == 0 else '✗'} Orders with shipped_date < order_date: {invalid_dates}"
|
|
)
|
|
assert invalid_dates == 0, "Found orders with invalid date logic"
|
|
|
|
# Check processing/cancelled orders have no shipped_date
|
|
cursor.execute(
|
|
"""
|
|
SELECT COUNT(*)
|
|
FROM orders
|
|
WHERE status IN ('Processing', 'Cancelled')
|
|
AND shipped_date IS NOT NULL
|
|
"""
|
|
)
|
|
invalid_processing = cursor.fetchone()[0]
|
|
print(
|
|
f"{'✓' if invalid_processing == 0 else '✗'} Processing/Cancelled orders with shipped_date: {invalid_processing}"
|
|
)
|
|
assert (
|
|
invalid_processing == 0
|
|
), "Processing/Cancelled orders should not have shipped_date"
|
|
|
|
|
|
def validate_status_distribution(cursor):
|
|
"""Validate order status distribution."""
|
|
print("\n" + "=" * 60)
|
|
print("STATUS DISTRIBUTION VALIDATION")
|
|
print("=" * 60)
|
|
|
|
cursor.execute(
|
|
"""
|
|
SELECT status,
|
|
COUNT(*) as count,
|
|
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM orders), 1) as percentage
|
|
FROM orders
|
|
GROUP BY status
|
|
ORDER BY count DESC
|
|
"""
|
|
)
|
|
|
|
expected_ranges = {
|
|
"Delivered": (70, 90),
|
|
"Shipped": (5, 20),
|
|
"Processing": (0, 15),
|
|
"Cancelled": (0, 5),
|
|
}
|
|
|
|
print("\nStatus Distribution:")
|
|
for status, count, percentage in cursor.fetchall():
|
|
min_pct, max_pct = expected_ranges.get(status, (0, 100))
|
|
status_ok = min_pct <= percentage <= max_pct
|
|
symbol = "✓" if status_ok else "✗"
|
|
print(
|
|
f" {symbol} {status}: {count} ({percentage}%) [expected: {min_pct}-{max_pct}%]"
|
|
)
|
|
assert status_ok, f"{status} percentage {percentage}% outside expected range"
|
|
|
|
|
|
def validate_order_totals(cursor):
|
|
"""Validate order totals match line items."""
|
|
print("\n" + "=" * 60)
|
|
print("ORDER TOTAL VALIDATION")
|
|
print("=" * 60)
|
|
|
|
cursor.execute(
|
|
"""
|
|
SELECT o.order_id,
|
|
o.total_amount as stored_total,
|
|
ROUND(COALESCE(SUM(oi.quantity * oi.price_per_unit), 0), 2) as calculated_total,
|
|
ABS(o.total_amount - COALESCE(SUM(oi.quantity * oi.price_per_unit), 0)) as difference
|
|
FROM orders o
|
|
LEFT JOIN order_items oi ON o.order_id = oi.order_id
|
|
GROUP BY o.order_id, o.total_amount
|
|
HAVING difference > 0.02
|
|
"""
|
|
)
|
|
|
|
mismatches = cursor.fetchall()
|
|
print(
|
|
f"{'✓' if len(mismatches) == 0 else '✗'} Orders with total mismatches: {len(mismatches)}"
|
|
)
|
|
|
|
if mismatches:
|
|
print("\nMismatched orders:")
|
|
for order_id, stored, calculated, diff in mismatches[:5]:
|
|
print(
|
|
f" {order_id}: stored=${stored}, calculated=${calculated}, diff=${diff}"
|
|
)
|
|
|
|
assert len(mismatches) == 0, "Found orders with total mismatches"
|
|
|
|
|
|
def validate_cancelled_orders(cursor):
|
|
"""Validate cancelled orders have no items and zero total."""
|
|
print("\n" + "=" * 60)
|
|
print("CANCELLED ORDERS VALIDATION")
|
|
print("=" * 60)
|
|
|
|
# Check cancelled orders have no items
|
|
cursor.execute(
|
|
"""
|
|
SELECT COUNT(DISTINCT oi.order_id)
|
|
FROM order_items oi
|
|
JOIN orders o ON oi.order_id = o.order_id
|
|
WHERE o.status = 'Cancelled'
|
|
"""
|
|
)
|
|
cancelled_with_items = cursor.fetchone()[0]
|
|
print(
|
|
f"{'✓' if cancelled_with_items == 0 else '✗'} Cancelled orders with items: {cancelled_with_items}"
|
|
)
|
|
assert cancelled_with_items == 0, "Cancelled orders should not have items"
|
|
|
|
# Check cancelled orders have zero total
|
|
cursor.execute(
|
|
"""
|
|
SELECT COUNT(*)
|
|
FROM orders
|
|
WHERE status = 'Cancelled'
|
|
AND total_amount != 0
|
|
"""
|
|
)
|
|
cancelled_nonzero = cursor.fetchone()[0]
|
|
print(
|
|
f"{'✓' if cancelled_nonzero == 0 else '✗'} Cancelled orders with non-zero total: {cancelled_nonzero}"
|
|
)
|
|
assert cancelled_nonzero == 0, "Cancelled orders should have zero total"
|
|
|
|
|
|
def validate_price_variations(cursor):
|
|
"""Validate price variations are within expected range."""
|
|
print("\n" + "=" * 60)
|
|
print("PRICE VARIATION VALIDATION")
|
|
print("=" * 60)
|
|
|
|
cursor.execute(
|
|
"""
|
|
SELECT p.product_id,
|
|
p.name,
|
|
p.price as current_price,
|
|
MIN(oi.price_per_unit) as min_historical,
|
|
MAX(oi.price_per_unit) as max_historical,
|
|
ROUND(ABS(MAX(oi.price_per_unit) - p.price) / p.price * 100, 1) as max_variance_pct
|
|
FROM products p
|
|
JOIN order_items oi ON p.product_id = oi.product_id
|
|
GROUP BY p.product_id, p.name, p.price
|
|
HAVING max_variance_pct > 5.5
|
|
"""
|
|
)
|
|
|
|
out_of_range = cursor.fetchall()
|
|
print(
|
|
f"{'✓' if len(out_of_range) == 0 else '✗'} Products with >5% price variance: {len(out_of_range)}"
|
|
)
|
|
|
|
if out_of_range:
|
|
print("\nProducts with high variance:")
|
|
for product_id, name, current, min_hist, max_hist, variance in out_of_range[:5]:
|
|
print(
|
|
f" {product_id}: current=${current}, range=${min_hist}-${max_hist}, variance={variance}%"
|
|
)
|
|
|
|
assert len(out_of_range) == 0, "Found products with excessive price variance"
|
|
|
|
|
|
def validate_customer_segments(cursor):
|
|
"""Validate customer segment distribution."""
|
|
print("\n" + "=" * 60)
|
|
print("CUSTOMER SEGMENT VALIDATION")
|
|
print("=" * 60)
|
|
|
|
cursor.execute(
|
|
"""
|
|
SELECT segment,
|
|
COUNT(*) as count,
|
|
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM customers), 1) as percentage
|
|
FROM customers
|
|
GROUP BY segment
|
|
ORDER BY count DESC
|
|
"""
|
|
)
|
|
|
|
print("\nSegment Distribution:")
|
|
for segment, count, percentage in cursor.fetchall():
|
|
print(f" {segment}: {count} ({percentage}%)")
|
|
|
|
|
|
def test_query_performance(cursor):
|
|
"""Test query performance for key workshop scenarios."""
|
|
print("\n" + "=" * 60)
|
|
print("QUERY PERFORMANCE TEST")
|
|
print("=" * 60)
|
|
|
|
queries = [
|
|
(
|
|
"Customer lookup",
|
|
"SELECT * FROM customers WHERE email = 'sarah.chen@gmail.com'",
|
|
),
|
|
("Order status", "SELECT * FROM orders WHERE customer_id = 'CUST-001'"),
|
|
(
|
|
"Order details",
|
|
"""
|
|
SELECT o.*, oi.*, p.name
|
|
FROM orders o
|
|
JOIN order_items oi ON o.order_id = oi.order_id
|
|
JOIN products p ON oi.product_id = p.product_id
|
|
WHERE o.customer_id = 'CUST-001'
|
|
""",
|
|
),
|
|
(
|
|
"Product availability",
|
|
"SELECT * FROM products WHERE category = 'Laptops' AND in_stock = 1",
|
|
),
|
|
(
|
|
"Bundle analysis",
|
|
"""
|
|
SELECT p1.name, p2.name, COUNT(*) as times
|
|
FROM order_items oi1
|
|
JOIN order_items oi2 ON oi1.order_id = oi2.order_id AND oi1.product_id < oi2.product_id
|
|
JOIN products p1 ON oi1.product_id = p1.product_id
|
|
JOIN products p2 ON oi2.product_id = p2.product_id
|
|
GROUP BY p1.name, p2.name
|
|
ORDER BY times DESC
|
|
LIMIT 10
|
|
""",
|
|
),
|
|
]
|
|
|
|
print("\nQuery execution times:")
|
|
all_under_100ms = True
|
|
|
|
for name, query in queries:
|
|
start = time.time()
|
|
cursor.execute(query)
|
|
cursor.fetchall()
|
|
elapsed_ms = (time.time() - start) * 1000
|
|
|
|
under_target = elapsed_ms < 100
|
|
symbol = "✓" if under_target else "✗"
|
|
print(f" {symbol} {name}: {elapsed_ms:.2f}ms")
|
|
|
|
if not under_target:
|
|
all_under_100ms = False
|
|
|
|
if all_under_100ms:
|
|
print("\n✓ All queries executed in <100ms")
|
|
else:
|
|
print("\n⚠ Some queries exceeded 100ms target (acceptable for small dataset)")
|
|
|
|
|
|
def run_sample_queries(cursor):
|
|
"""Run a few sample workshop queries."""
|
|
print("\n" + "=" * 60)
|
|
print("SAMPLE QUERY RESULTS")
|
|
print("=" * 60)
|
|
|
|
# Customer verification
|
|
print("\n1. Customer verification (HITL scenario):")
|
|
cursor.execute(
|
|
"SELECT customer_id, name, email FROM customers WHERE email = 'sarah.chen@gmail.com'"
|
|
)
|
|
result = cursor.fetchone()
|
|
if result:
|
|
print(f" Found: {result[1]} ({result[0]}) - {result[2]}")
|
|
|
|
# Recent orders
|
|
print("\n2. Recent orders for CUST-001:")
|
|
cursor.execute(
|
|
"""
|
|
SELECT order_id, order_date, status, total_amount
|
|
FROM orders
|
|
WHERE customer_id = 'CUST-001'
|
|
ORDER BY order_date DESC
|
|
LIMIT 3
|
|
"""
|
|
)
|
|
for order_id, date, status, total in cursor.fetchall():
|
|
print(f" {order_id}: {date} - {status} (${total})")
|
|
|
|
# Top product bundles
|
|
print("\n3. Top product combinations:")
|
|
cursor.execute(
|
|
"""
|
|
SELECT p1.name as product1, p2.name as product2, COUNT(*) as times
|
|
FROM order_items oi1
|
|
JOIN order_items oi2 ON oi1.order_id = oi2.order_id AND oi1.product_id < oi2.product_id
|
|
JOIN products p1 ON oi1.product_id = p1.product_id
|
|
JOIN products p2 ON oi2.product_id = p2.product_id
|
|
GROUP BY p1.name, p2.name
|
|
ORDER BY times DESC
|
|
LIMIT 3
|
|
"""
|
|
)
|
|
for prod1, prod2, times in cursor.fetchall():
|
|
print(f" {times}x: {prod1} + {prod2}")
|
|
|
|
|
|
def main():
|
|
"""Main validation execution."""
|
|
print("=" * 60)
|
|
print("TechHub Database Validator")
|
|
print("=" * 60)
|
|
print(f"Database: {DB_PATH}")
|
|
|
|
try:
|
|
conn = connect_database()
|
|
cursor = conn.cursor()
|
|
|
|
# Run all validations
|
|
validate_record_counts(cursor)
|
|
validate_foreign_keys(cursor)
|
|
validate_date_logic(cursor)
|
|
validate_status_distribution(cursor)
|
|
validate_order_totals(cursor)
|
|
validate_cancelled_orders(cursor)
|
|
validate_price_variations(cursor)
|
|
validate_customer_segments(cursor)
|
|
test_query_performance(cursor)
|
|
run_sample_queries(cursor)
|
|
|
|
print("\n" + "=" * 60)
|
|
print("✓ ALL VALIDATIONS PASSED")
|
|
print("=" * 60)
|
|
print("\nDatabase is ready for workshop use!")
|
|
print("Next steps:")
|
|
print(" - Try queries from scripts/sample_queries.sql")
|
|
print(" - Build multi-agent system using this database")
|
|
print(" - Create RAG documentation for complete dataset")
|
|
|
|
except AssertionError as e:
|
|
print("\n" + "=" * 60)
|
|
print(f"✗ VALIDATION FAILED: {e}")
|
|
print("=" * 60)
|
|
return 1
|
|
except Exception as e:
|
|
print("\n" + "=" * 60)
|
|
print(f"✗ ERROR: {e}")
|
|
print("=" * 60)
|
|
return 1
|
|
finally:
|
|
conn.close()
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
exit(main())
|