#!/usr/bin/env python3 """ Seed data script for Patherly decision trees. This script creates example troubleshooting trees in the database. Run from the backend directory with: python -m scripts.seed_data Requirements: - Backend server must be running (uvicorn app.main:app) - Or run with --direct flag to insert directly to database """ import asyncio import argparse import httpx from typing import Any # API Configuration API_BASE_URL = "http://localhost:8000/api/v1" # Default admin user for seeding (will be created if doesn't exist) SEED_USER = { "email": "seed.admin@example.com", "password": "SeedAdmin123!", "name": "Seed Admin", "role": "admin" } # ============================================================================= # TREE DEFINITIONS # ============================================================================= def get_password_reset_tree() -> dict[str, Any]: """ Password Reset Request - Simple troubleshooting tree. Based on TS-EXAMPLES.md Scenario 5. """ return { "name": "Password Reset Request", "description": "Guide for handling user password reset requests safely and efficiently. Covers identity verification, account status checks, and common issues.", "category": "Account Management", "tree_structure": { "id": "root", "type": "decision", "question": "Can you verify the user's identity?", "help_text": "Check against company verification policy (phone callback, email verification, or manager confirmation)", "options": [ {"id": "verified", "label": "Yes, identity verified", "next_node_id": "find_account"}, {"id": "not_verified", "label": "No, cannot verify", "next_node_id": "deny_request"}, {"id": "contractor", "label": "User is a contractor", "next_node_id": "contractor_approval"} ], "children": [ { "id": "deny_request", "type": "solution", "title": "Deny Request - Cannot Verify Identity", "description": "Inform the user that you cannot process their request without proper identity verification.\n\n**Actions:**\n- Politely explain the verification requirements\n- Provide alternative verification methods they can use\n- Document the denied request in the ticket system\n\n**Do NOT reset the password under any circumstances.**" }, { "id": "contractor_approval", "type": "decision", "question": "Does the contractor's manager approve the reset?", "help_text": "Per company policy, contractor password resets require manager approval", "options": [ {"id": "manager_approves", "label": "Manager approves", "next_node_id": "find_account"}, {"id": "manager_denies", "label": "Manager denies", "next_node_id": "contractor_denied"}, {"id": "manager_unavailable", "label": "Cannot reach manager", "next_node_id": "escalate_manager"} ], "children": [ { "id": "contractor_denied", "type": "solution", "title": "Request Denied by Manager", "description": "Inform the contractor that their manager has denied the password reset request.\n\n**Actions:**\n- Document the denial in the ticket\n- Advise contractor to follow up with their manager directly\n- Close the ticket as 'Denied'" }, { "id": "escalate_manager", "type": "solution", "title": "Escalate to IT Manager", "description": "Cannot reach contractor's manager for approval.\n\n**Actions:**\n- Escalate ticket to IT Manager for decision\n- Document attempts to reach the contractor's manager\n- Set ticket status to 'Pending Approval'\n\n**Do NOT reset the password until approval is received.**" } ] }, { "id": "find_account", "type": "decision", "question": "Can you find the user's account in Active Directory?", "help_text": "Search AD by username, email, or employee name", "options": [ {"id": "account_found", "label": "Account found", "next_node_id": "check_account_status"}, {"id": "account_not_found", "label": "Account not found", "next_node_id": "check_spelling"}, {"id": "multiple_accounts", "label": "Multiple accounts found", "next_node_id": "identify_account"} ], "children": [ { "id": "check_spelling", "type": "decision", "question": "Is the username spelled correctly?", "help_text": "Verify spelling with the user, check for common variations", "options": [ {"id": "found_correct", "label": "Found with correct spelling", "next_node_id": "check_account_status"}, {"id": "still_not_found", "label": "Still not found", "next_node_id": "no_account_exists"}, {"id": "user_no_account", "label": "User doesn't have an account", "next_node_id": "no_account_exists"} ], "children": [ { "id": "no_account_exists", "type": "solution", "title": "Account Does Not Exist", "description": "The user does not have an Active Directory account.\n\n**Actions:**\n- Verify with HR if user should have an account\n- If new employee: Route to New User Request process\n- If existing employee: Escalate to AD team for investigation\n- Document findings in the ticket" } ] }, { "id": "identify_account", "type": "decision", "question": "Can you identify the correct account?", "help_text": "Use employee ID, department, manager name, or start date to identify", "options": [ {"id": "identified_by_id", "label": "Identified by employee ID", "next_node_id": "check_account_status"}, {"id": "identified_by_dept", "label": "Identified by department", "next_node_id": "check_account_status"}, {"id": "cannot_identify", "label": "Cannot identify correct account", "next_node_id": "need_more_info"} ], "children": [ { "id": "need_more_info", "type": "solution", "title": "Request Additional Information", "description": "Cannot determine which account belongs to the user.\n\n**Actions:**\n- Ask user for:\n - Employee ID\n - Department\n - Manager's name\n - Hire/start date\n - Last known working username\n- Once identified, continue with account status check" } ] }, { "id": "check_account_status", "type": "decision", "question": "What is the account status?", "help_text": "Check if account is enabled, disabled, or locked in AD", "options": [ {"id": "enabled", "label": "Account is enabled", "next_node_id": "reset_password"}, {"id": "disabled", "label": "Account is disabled", "next_node_id": "check_disabled_reason"}, {"id": "locked", "label": "Account is locked out", "next_node_id": "unlock_account"} ], "children": [ { "id": "check_disabled_reason", "type": "decision", "question": "Why is the account disabled?", "help_text": "Check account notes, ticket history, or HR records", "options": [ {"id": "terminated", "label": "Disabled for termination", "next_node_id": "terminated_user"}, {"id": "inactive", "label": "Disabled for inactivity", "next_node_id": "verify_employment"}, {"id": "disabled_error", "label": "Disabled in error", "next_node_id": "enable_and_reset"} ], "children": [ { "id": "terminated_user", "type": "solution", "title": "Account Disabled - User Terminated", "description": "**DO NOT ENABLE THIS ACCOUNT**\n\nThe account was disabled because the user has been terminated.\n\n**Actions:**\n- Inform the requester that the account cannot be enabled\n- If this is an error, direct them to HR to verify employment status\n- Document the request and denial in the ticket\n- Close ticket as 'Denied - Terminated User'" }, { "id": "verify_employment", "type": "decision", "question": "Is the user still employed?", "help_text": "Verify with HR or the user's manager", "options": [ {"id": "still_employed", "label": "Still employed", "next_node_id": "enable_and_reset"}, {"id": "terminated_confirmed", "label": "Terminated", "next_node_id": "terminated_user"}, {"id": "unknown_status", "label": "Unknown status", "next_node_id": "escalate_employment"} ], "children": [ { "id": "escalate_employment", "type": "solution", "title": "Escalate to IT Manager", "description": "Cannot verify employment status.\n\n**Actions:**\n- Escalate ticket to IT Manager\n- Document all verification attempts\n- Do NOT enable the account until status is confirmed" }, { "id": "enable_and_reset", "type": "action", "title": "Enable Account and Reset Password", "description": "Account should be re-enabled.\n\n**Actions:**\n1. Enable the account in Active Directory\n2. Continue to password reset step", "next_node_id": "reset_password" } ] } ] }, { "id": "unlock_account", "type": "action", "title": "Unlock Account", "description": "Account is locked out, likely from too many failed login attempts.\n\n**Actions:**\n1. Unlock the account in Active Directory\n2. Continue to reset password", "action_command": "Unlock-ADAccount -Identity USERNAME", "next_node_id": "reset_password" }, { "id": "reset_password", "type": "decision", "question": "Were you able to reset the password?", "help_text": "Set a temporary password in AD with 'User must change password at next logon' checked", "action_command": "Set-ADAccountPassword -Identity USERNAME -Reset -NewPassword (ConvertTo-SecureString 'TempPass123!' -AsPlainText -Force)", "options": [ {"id": "reset_success", "label": "Reset successful", "next_node_id": "communicate_password"}, {"id": "reset_denied", "label": "Permission denied", "next_node_id": "escalate_reset"}, {"id": "reset_but_fails", "label": "Reset but user still can't login", "next_node_id": "check_login_issue"} ], "children": [ { "id": "escalate_reset", "type": "solution", "title": "Escalate to Higher-Level Admin", "description": "You don't have permission to reset this user's password.\n\n**This typically happens for:**\n- Executive accounts\n- Service accounts\n- Accounts in protected OUs\n\n**Actions:**\n- Escalate to Tier 2 or AD Administrator\n- Document the account and reason for escalation" }, { "id": "check_login_issue", "type": "decision", "question": "What is preventing login?", "help_text": "Common issues after password reset", "options": [ {"id": "wrong_username", "label": "User entering wrong username", "next_node_id": "provide_username"}, {"id": "caps_lock", "label": "Caps Lock is on", "next_node_id": "caps_lock_fix"}, {"id": "not_synced", "label": "Password not synced yet", "next_node_id": "wait_sync"}, {"id": "mfa_issue", "label": "MFA/2FA issue", "next_node_id": "mfa_separate"} ], "children": [ { "id": "provide_username", "type": "solution", "title": "Provide Correct Username", "description": "User is entering the wrong username.\n\n**Actions:**\n- Provide the correct username format (e.g., jsmith or john.smith@company.com)\n- Have user try again with correct credentials\n- Verify successful login" }, { "id": "caps_lock_fix", "type": "solution", "title": "Caps Lock Issue", "description": "User has Caps Lock enabled.\n\n**Actions:**\n- Inform user to turn off Caps Lock\n- Have user retry login\n- Verify successful login" }, { "id": "wait_sync", "type": "solution", "title": "Wait for Password Sync", "description": "Password may not have synchronized to all systems yet.\n\n**Actions:**\n- Wait 2-5 minutes for AD replication\n- If using Azure AD Connect, sync may take up to 30 minutes\n- Have user retry after waiting\n- If still failing after 30 minutes, check replication status" }, { "id": "mfa_separate", "type": "solution", "title": "MFA Issue - Separate Troubleshooting", "description": "User has a Multi-Factor Authentication issue.\n\n**This is a separate troubleshooting path.**\n\n**Common MFA issues:**\n- Authenticator app not set up\n- Phone number changed\n- Hardware token lost\n\n**Actions:**\n- Create a new ticket for MFA troubleshooting\n- Or escalate to Identity Management team" } ] }, { "id": "communicate_password", "type": "decision", "question": "How was the password communicated to the user?", "help_text": "Ensure secure delivery of temporary password", "options": [ {"id": "told_phone", "label": "Told over phone", "next_node_id": "verify_login"}, {"id": "secure_portal", "label": "Sent via secure portal", "next_node_id": "verify_login"}, {"id": "user_received", "label": "User confirmed receipt", "next_node_id": "verify_login"} ], "children": [ { "id": "verify_login", "type": "decision", "question": "Was the user able to log in successfully?", "help_text": "Confirm with user that they can access their account", "options": [ {"id": "login_success", "label": "Login successful", "next_node_id": "resolution_success"}, {"id": "login_failed", "label": "Still cannot log in", "next_node_id": "check_login_issue"} ], "children": [ { "id": "resolution_success", "type": "solution", "title": "[OK] Password Reset Complete", "description": "User has successfully logged in with their new password.\n\n**Final Steps:**\n1. Confirm user was prompted to change their password\n2. Verify user successfully set a new personal password\n3. Document the resolution in the ticket\n4. Close the ticket as 'Resolved'\n\n**Resolution Indicators:**\n- User confirms successful login\n- Account shows updated 'Last Logon' timestamp\n- No subsequent lockouts or reset requests" } ] } ] } ] } ] } ] } ] } } def get_file_share_access_tree() -> dict[str, Any]: """ User Cannot Access File Share - Medium complexity troubleshooting tree. Based on TS-EXAMPLES.md Scenario 3. (Stub for future implementation) """ return { "name": "File Share Access Issues", "description": "Troubleshoot user access to network file shares. Covers network connectivity, permissions, and SMB issues.", "category": "File Services", "tree_structure": { "id": "root", "type": "decision", "question": "Can the user ping the file server by name?", "options": [ {"id": "ping_success", "label": "Yes, ping succeeds", "next_node_id": "check_share_access"}, {"id": "ping_timeout", "label": "No, request timed out", "next_node_id": "network_issue"}, {"id": "unknown_host", "label": "Unknown host error", "next_node_id": "dns_issue"} ], "children": [ { "id": "check_share_access", "type": "solution", "title": "Check Share Access (Placeholder)", "description": "This tree will be expanded in a future update.\n\nFor now, check:\n1. Share permissions\n2. NTFS permissions\n3. User group memberships" }, { "id": "network_issue", "type": "solution", "title": "Network Connectivity Issue (Placeholder)", "description": "This tree will be expanded in a future update.\n\nCheck:\n1. VPN status if remote\n2. Network cable/WiFi connection\n3. Firewall rules" }, { "id": "dns_issue", "type": "solution", "title": "DNS Resolution Issue (Placeholder)", "description": "This tree will be expanded in a future update.\n\nRun: nslookup FILE-SERVER-NAME\n\nCheck DNS configuration on user's PC." } ] } } # ============================================================================= # SEEDING FUNCTIONS # ============================================================================= async def get_or_create_admin_user(client: httpx.AsyncClient) -> tuple[str, dict]: """Get authentication token for seeding. Creates admin user if needed.""" # Try to login first login_response = await client.post( f"{API_BASE_URL}/auth/login/json", json={"email": SEED_USER["email"], "password": SEED_USER["password"]} ) if login_response.status_code == 200: token_data = login_response.json() return token_data["access_token"], {"exists": True} # User doesn't exist, create them register_response = await client.post( f"{API_BASE_URL}/auth/register", json=SEED_USER ) if register_response.status_code not in (200, 201): raise Exception(f"Failed to create seed user: {register_response.text}") # Now login login_response = await client.post( f"{API_BASE_URL}/auth/login/json", json={"email": SEED_USER["email"], "password": SEED_USER["password"]} ) if login_response.status_code != 200: raise Exception(f"Failed to login as seed user: {login_response.text}") token_data = login_response.json() return token_data["access_token"], {"exists": False, "user": register_response.json()} async def create_tree(client: httpx.AsyncClient, token: str, tree_data: dict) -> dict: """Create a tree via the API.""" headers = {"Authorization": f"Bearer {token}"} # Check if tree with same name exists list_response = await client.get(f"{API_BASE_URL}/trees", headers=headers) if list_response.status_code == 200: existing_trees = list_response.json() for tree in existing_trees: if tree["name"] == tree_data["name"]: print(f" [SKIP] Tree '{tree_data['name']}' already exists (ID: {tree['id']})") return tree # Create the tree response = await client.post( f"{API_BASE_URL}/trees", json=tree_data, headers=headers ) if response.status_code not in (200, 201): raise Exception(f"Failed to create tree '{tree_data['name']}': {response.text}") tree = response.json() print(f" [OK] Created tree '{tree_data['name']}' (ID: {tree['id']})") return tree async def seed_database(): """Main seeding function.""" print("\n[*] Patherly Database Seeder") print("=" * 50) async with httpx.AsyncClient(timeout=30.0) as client: # Check if API is running try: health_check = await client.get(f"{API_BASE_URL.replace('/api/v1', '')}/health") except httpx.ConnectError: print("\n[ERROR] Error: Cannot connect to API server") print(f" Make sure the server is running at {API_BASE_URL}") print(" Run: uvicorn app.main:app --reload") return False # Get or create admin user print("\n[1/2] Setting up seed user...") try: token, user_info = await get_or_create_admin_user(client) if user_info.get("exists"): print(" [OK] Using existing seed admin user") else: print(f" [OK] Created seed admin user: {SEED_USER['email']}") except Exception as e: print(f" [ERROR] Failed to setup seed user: {e}") return False # Create trees print("\n[2/2] Creating decision trees...") trees_to_create = [ get_password_reset_tree(), get_file_share_access_tree(), ] created_trees = [] for tree_data in trees_to_create: try: tree = await create_tree(client, token, tree_data) created_trees.append(tree) except Exception as e: print(f" [ERROR] Failed to create '{tree_data['name']}': {e}") # Summary print("\n" + "=" * 50) print(f"[OK] Seeding complete! Created {len(created_trees)} trees.") print("\nCreated trees:") for tree in created_trees: print(f" - {tree['name']} ({tree['category']})") return True def main(): parser = argparse.ArgumentParser(description="Seed the Patherly database with example trees") parser.add_argument("--direct", action="store_true", help="Insert directly to database (not implemented)") args = parser.parse_args() if args.direct: print("Direct database insertion not yet implemented. Using API method.") success = asyncio.run(seed_database()) exit(0 if success else 1) if __name__ == "__main__": main()