Update APP_NAME, OpenAPI metadata, log messages, root endpoint response, model docstrings, seed script comments, README heading, and CLAUDE.md branding references. Frontend rebrand was completed in PR #26; this covers everything else. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
474 lines
28 KiB
Python
474 lines
28 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Seed data script for ResolutionFlow 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[*] ResolutionFlow 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 ResolutionFlow 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()
|