Ryan Malloy 44ed9936b7 Initial commit: Claude Code Project Tracker
Add comprehensive development intelligence system that tracks:
- Development sessions with automatic start/stop
- Full conversation history with semantic search
- Tool usage and file operation analytics
- Think time and engagement analysis
- Git activity correlation
- Learning pattern recognition
- Productivity insights and metrics

Features:
- FastAPI backend with SQLite database
- Modern web dashboard with interactive charts
- Claude Code hook integration for automatic tracking
- Comprehensive test suite with 100+ tests
- Complete API documentation (OpenAPI/Swagger)
- Privacy-first design with local data storage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 02:59:21 -06:00

413 lines
17 KiB
Python

"""
Test fixtures and sample data for the Claude Code Project Tracker.
"""
from datetime import datetime, timedelta
from typing import Dict, List, Any
from faker import Faker
fake = Faker()
class TestDataFactory:
"""Factory for creating realistic test data."""
@staticmethod
def create_project_data(**overrides) -> Dict[str, Any]:
"""Create sample project data."""
data = {
"name": fake.company(),
"path": fake.file_path(depth=3, extension=""),
"git_repo": fake.url().replace("http://", "https://github.com/"),
"languages": fake.random_elements(
elements=["python", "javascript", "typescript", "go", "rust", "java", "cpp"],
length=fake.random_int(min=1, max=4),
unique=True
),
"created_at": fake.date_time_between(start_date="-1y", end_date="now"),
"last_session": fake.date_time_between(start_date="-30d", end_date="now"),
"total_sessions": fake.random_int(min=1, max=50),
"total_time_minutes": fake.random_int(min=30, max=2000),
"files_modified_count": fake.random_int(min=5, max=100),
"lines_changed_count": fake.random_int(min=100, max=5000)
}
data.update(overrides)
return data
@staticmethod
def create_session_data(project_id: int = 1, **overrides) -> Dict[str, Any]:
"""Create sample session data."""
start_time = fake.date_time_between(start_date="-7d", end_date="now")
duration = fake.random_int(min=5, max=180) # 5 minutes to 3 hours
data = {
"project_id": project_id,
"start_time": start_time,
"end_time": start_time + timedelta(minutes=duration),
"session_type": fake.random_element(elements=["startup", "resume", "clear"]),
"working_directory": fake.file_path(depth=3, extension=""),
"git_branch": fake.random_element(elements=["main", "develop", "feature/new-feature", "bugfix/issue-123"]),
"environment": {
"user": fake.user_name(),
"pwd": fake.file_path(depth=3, extension=""),
"python_version": "3.11.0",
"node_version": "18.17.0"
},
"duration_minutes": duration,
"activity_count": fake.random_int(min=3, max=50),
"conversation_count": fake.random_int(min=2, max=30),
"files_touched": [fake.file_path() for _ in range(fake.random_int(min=1, max=8))]
}
data.update(overrides)
return data
@staticmethod
def create_conversation_data(session_id: int = 1, **overrides) -> Dict[str, Any]:
"""Create sample conversation data."""
prompts = [
"How do I implement user authentication?",
"Can you help me debug this error?",
"What's the best way to structure this code?",
"Help me optimize this function for performance",
"How do I add tests for this component?",
"Can you review this code for best practices?",
"What libraries should I use for this feature?",
"How do I handle errors in this async function?"
]
responses = [
"I can help you implement user authentication. Here's a comprehensive approach...",
"Let me analyze this error and provide a solution...",
"For code structure, I recommend following these patterns...",
"Here are several optimization strategies for your function...",
"Let's add comprehensive tests for this component...",
"I've reviewed your code and here are my recommendations...",
"For this feature, I suggest these well-maintained libraries...",
"Here's how to properly handle errors in async functions..."
]
data = {
"session_id": session_id,
"timestamp": fake.date_time_between(start_date="-7d", end_date="now"),
"user_prompt": fake.random_element(elements=prompts),
"claude_response": fake.random_element(elements=responses),
"tools_used": fake.random_elements(
elements=["Edit", "Write", "Read", "Bash", "Grep", "Glob", "Task"],
length=fake.random_int(min=1, max=4),
unique=True
),
"files_affected": [
fake.file_path(extension=ext)
for ext in fake.random_elements(
elements=["py", "js", "ts", "go", "rs", "java", "cpp", "md"],
length=fake.random_int(min=0, max=3),
unique=True
)
],
"context": {
"intent": fake.random_element(elements=["debugging", "implementation", "learning", "optimization"]),
"complexity": fake.random_element(elements=["low", "medium", "high"])
},
"tokens_input": fake.random_int(min=50, max=500),
"tokens_output": fake.random_int(min=100, max=1000),
"exchange_type": fake.random_element(elements=["user_prompt", "claude_response"])
}
data.update(overrides)
return data
@staticmethod
def create_activity_data(session_id: int = 1, **overrides) -> Dict[str, Any]:
"""Create sample activity data."""
tools = {
"Edit": {
"action": "file_edit",
"metadata": {"lines_changed": fake.random_int(min=1, max=50)},
"lines_added": fake.random_int(min=0, max=30),
"lines_removed": fake.random_int(min=0, max=20)
},
"Write": {
"action": "file_write",
"metadata": {"new_file": fake.boolean()},
"lines_added": fake.random_int(min=10, max=100),
"lines_removed": 0
},
"Read": {
"action": "file_read",
"metadata": {"file_size": fake.random_int(min=100, max=5000)},
"lines_added": 0,
"lines_removed": 0
},
"Bash": {
"action": "command_execution",
"metadata": {
"command": fake.random_element(elements=[
"npm install",
"pytest",
"git status",
"python main.py",
"docker build ."
]),
"exit_code": 0
},
"lines_added": 0,
"lines_removed": 0
},
"Grep": {
"action": "search",
"metadata": {
"pattern": fake.word(),
"matches_found": fake.random_int(min=0, max=20)
},
"lines_added": 0,
"lines_removed": 0
}
}
tool_name = fake.random_element(elements=list(tools.keys()))
tool_config = tools[tool_name]
data = {
"session_id": session_id,
"timestamp": fake.date_time_between(start_date="-7d", end_date="now"),
"tool_name": tool_name,
"action": tool_config["action"],
"file_path": fake.file_path() if tool_name in ["Edit", "Write", "Read"] else None,
"metadata": tool_config["metadata"],
"success": fake.boolean(chance_of_getting_true=90),
"error_message": None if fake.boolean(chance_of_getting_true=90) else "Operation failed",
"lines_added": tool_config.get("lines_added", 0),
"lines_removed": tool_config.get("lines_removed", 0)
}
data.update(overrides)
return data
@staticmethod
def create_waiting_period_data(session_id: int = 1, **overrides) -> Dict[str, Any]:
"""Create sample waiting period data."""
start_time = fake.date_time_between(start_date="-7d", end_date="now")
duration = fake.random_int(min=5, max=300) # 5 seconds to 5 minutes
activities = {
"thinking": "User is contemplating the response",
"research": "User is looking up documentation",
"external_work": "User is working in another application",
"break": "User stepped away from the computer"
}
likely_activity = fake.random_element(elements=list(activities.keys()))
data = {
"session_id": session_id,
"start_time": start_time,
"end_time": start_time + timedelta(seconds=duration),
"duration_seconds": duration,
"context_before": "Claude finished providing a detailed explanation",
"context_after": fake.random_element(elements=[
"User asked a follow-up question",
"User requested clarification",
"User provided additional context",
"User asked about a different topic"
]),
"likely_activity": likely_activity
}
data.update(overrides)
return data
@staticmethod
def create_git_operation_data(session_id: int = 1, **overrides) -> Dict[str, Any]:
"""Create sample git operation data."""
operations = {
"commit": {
"command": "git commit -m 'Add new feature'",
"result": "[main abc123] Add new feature\\n 2 files changed, 15 insertions(+), 3 deletions(-)",
"files_changed": ["main.py", "utils.py"],
"lines_added": 15,
"lines_removed": 3,
"commit_hash": fake.sha1()[:8]
},
"push": {
"command": "git push origin main",
"result": "To https://github.com/user/repo.git\\n abc123..def456 main -> main",
"files_changed": [],
"lines_added": 0,
"lines_removed": 0,
"commit_hash": None
},
"pull": {
"command": "git pull origin main",
"result": "Already up to date.",
"files_changed": [],
"lines_added": 0,
"lines_removed": 0,
"commit_hash": None
},
"branch": {
"command": "git checkout -b feature/new-feature",
"result": "Switched to a new branch 'feature/new-feature'",
"files_changed": [],
"lines_added": 0,
"lines_removed": 0,
"commit_hash": None
}
}
operation = fake.random_element(elements=list(operations.keys()))
op_config = operations[operation]
data = {
"session_id": session_id,
"timestamp": fake.date_time_between(start_date="-7d", end_date="now"),
"operation": operation,
"command": op_config["command"],
"result": op_config["result"],
"success": fake.boolean(chance_of_getting_true=95),
"files_changed": op_config["files_changed"],
"lines_added": op_config["lines_added"],
"lines_removed": op_config["lines_removed"],
"commit_hash": op_config["commit_hash"],
"branch_from": fake.random_element(elements=["main", "develop", "feature/old-feature"]) if operation == "merge" else None,
"branch_to": fake.random_element(elements=["main", "develop"]) if operation == "merge" else None
}
data.update(overrides)
return data
class SampleDataSets:
"""Pre-defined sample data sets for different test scenarios."""
@staticmethod
def productive_session() -> Dict[str, List[Dict[str, Any]]]:
"""Data for a highly productive development session."""
project_data = TestDataFactory.create_project_data(
name="E-commerce Platform",
languages=["python", "javascript", "typescript"],
total_sessions=25,
total_time_minutes=800
)
session_data = TestDataFactory.create_session_data(
session_type="startup",
duration_minutes=120,
activity_count=45,
conversation_count=12
)
conversations = [
TestDataFactory.create_conversation_data(
user_prompt="How do I implement user authentication with JWT?",
tools_used=["Edit", "Write"],
files_affected=["auth.py", "models.py"]
),
TestDataFactory.create_conversation_data(
user_prompt="Can you help me optimize this database query?",
tools_used=["Edit", "Read"],
files_affected=["queries.py"]
),
TestDataFactory.create_conversation_data(
user_prompt="What's the best way to handle async errors?",
tools_used=["Edit"],
files_affected=["error_handler.py"]
)
]
activities = [
TestDataFactory.create_activity_data(tool_name="Edit", lines_added=25, lines_removed=5),
TestDataFactory.create_activity_data(tool_name="Write", lines_added=50, lines_removed=0),
TestDataFactory.create_activity_data(tool_name="Bash", metadata={"command": "pytest"}),
TestDataFactory.create_activity_data(tool_name="Read", file_path="/docs/api.md")
]
return {
"project": project_data,
"session": session_data,
"conversations": conversations,
"activities": activities
}
@staticmethod
def debugging_session() -> Dict[str, List[Dict[str, Any]]]:
"""Data for a debugging-focused session with lots of investigation."""
project_data = TestDataFactory.create_project_data(
name="Bug Tracking System",
languages=["go", "typescript"]
)
session_data = TestDataFactory.create_session_data(
session_type="resume",
duration_minutes=90,
activity_count=35,
conversation_count=8
)
conversations = [
TestDataFactory.create_conversation_data(
user_prompt="I'm getting a panic in my Go application, can you help debug?",
tools_used=["Read", "Grep"],
files_affected=["main.go", "handler.go"],
context={"intent": "debugging", "complexity": "high"}
),
TestDataFactory.create_conversation_data(
user_prompt="The tests are failing intermittently, what could be wrong?",
tools_used=["Read", "Bash"],
files_affected=["test_handler.go"]
)
]
# Lots of read operations for debugging
activities = [
TestDataFactory.create_activity_data(tool_name="Read") for _ in range(8)
] + [
TestDataFactory.create_activity_data(tool_name="Grep", metadata={"pattern": "error", "matches_found": 12}),
TestDataFactory.create_activity_data(tool_name="Bash", metadata={"command": "go test -v"}),
TestDataFactory.create_activity_data(tool_name="Edit", lines_added=3, lines_removed=1)
]
# Longer thinking periods during debugging
waiting_periods = [
TestDataFactory.create_waiting_period_data(
duration_seconds=180,
likely_activity="research",
context_after="User asked about error patterns"
),
TestDataFactory.create_waiting_period_data(
duration_seconds=120,
likely_activity="thinking",
context_after="User provided stack trace"
)
]
return {
"project": project_data,
"session": session_data,
"conversations": conversations,
"activities": activities,
"waiting_periods": waiting_periods
}
@staticmethod
def learning_session() -> Dict[str, List[Dict[str, Any]]]:
"""Data for a learning-focused session with lots of questions."""
project_data = TestDataFactory.create_project_data(
name="Learning Rust",
languages=["rust"],
total_sessions=5,
total_time_minutes=200
)
conversations = [
TestDataFactory.create_conversation_data(
user_prompt="What's the difference between String and &str in Rust?",
context={"intent": "learning", "complexity": "medium"}
),
TestDataFactory.create_conversation_data(
user_prompt="How do I handle ownership and borrowing correctly?",
context={"intent": "learning", "complexity": "high"}
),
TestDataFactory.create_conversation_data(
user_prompt="Can you explain Rust's trait system?",
context={"intent": "learning", "complexity": "high"}
)
]
return {
"project": project_data,
"conversations": conversations
}