# Technical Debt Assessment - mockupAWS v1.0.0

> **Version:** 1.0.0  
> **Author:** @spec-architect  
> **Date:** 2026-04-07  
> **Status:** DRAFT - Ready for Review  

---

## Executive Summary

This document provides a comprehensive technical debt assessment for the mockupAWS codebase in preparation for v1.0.0 production release. The assessment covers code quality, architectural debt, test coverage gaps, and prioritizes remediation efforts.

### Key Findings Overview

| Category | Issues Found | Critical | High | Medium | Low |
|----------|-------------|----------|------|--------|-----|
| Code Quality | 23 | 2 | 5 | 10 | 6 |
| Test Coverage | 8 | 1 | 2 | 3 | 2 |
| Architecture | 12 | 3 | 4 | 3 | 2 |
| Documentation | 6 | 0 | 1 | 3 | 2 |
| **Total** | **49** | **6** | **12** | **19** | **12** |

### Debt Quadrant Analysis

```
                    High Impact
                         │
        ┌────────────────┼────────────────┐
        │   DELIBERATE   │   RECKLESS     │
        │   (Prudent)    │   (Inadvertent)│
        │                │                │
        │ • MVP shortcuts│ • Missing tests│
        │ • Known tech   │ • No monitoring│
        │   limitations  │ • Quick fixes  │
        │                │                │
────────┼────────────────┼────────────────┼────────
        │                │                │
        │ • Architectural│ • Copy-paste   │
        │   decisions    │   code         │
        │ • Version      │ • No docs      │
        │   pinning      │ • Spaghetti    │
        │                │   code         │
        │   PRUDENT      │   RECKLESS     │
        └────────────────┼────────────────┘
                         │
                    Low Impact
```

---

## 1. Code Quality Analysis

### 1.1 Backend Code Analysis

#### Complexity Metrics (Radon)

```bash
# Install radon
pip install radon

# Generate complexity report
radon cc src/ -a -nc

# Results summary
```

**Cyclomatic Complexity Findings:**

| File | Function | Complexity | Rank | Action |
|------|----------|------------|------|--------|
| `cost_calculator.py` | `calculate_total_cost` | 15 | F | Refactor |
| `ingest_service.py` | `ingest_log` | 12 | F | Refactor |
| `report_service.py` | `generate_pdf_report` | 11 | F | Refactor |
| `auth_service.py` | `authenticate_user` | 8 | C | Monitor |
| `pii_detector.py` | `detect_pii` | 7 | C | Monitor |

**High Complexity Hotspots:**

```python
# src/services/cost_calculator.py - Complexity: 15 (TOO HIGH)
# REFACTOR: Break into smaller functions

class CostCalculator:
    def calculate_total_cost(self, metrics: List[Metric]) -> Decimal:
        """Calculate total cost - CURRENT: 15 complexity"""
        total = Decimal('0')
        
        # 1. Calculate SQS costs
        for metric in metrics:
            if metric.metric_type == 'sqs':
                if metric.region in ['us-east-1', 'us-west-2']:
                    if metric.value > 1000000:  # Tiered pricing
                        total += self._calculate_sqs_high_tier(metric)
                    else:
                        total += self._calculate_sqs_standard(metric)
                else:
                    total += self._calculate_sqs_other_regions(metric)
        
        # 2. Calculate Lambda costs
        for metric in metrics:
            if metric.metric_type == 'lambda':
                if metric.extra_data.get('memory') > 1024:
                    total += self._calculate_lambda_high_memory(metric)
                else:
                    total += self._calculate_lambda_standard(metric)
        
        # 3. Calculate Bedrock costs (continues...)
        # 15+ branches in this function!
        
        return total

# REFACTORED VERSION - Target complexity: < 5 per function
class CostCalculator:
    def calculate_total_cost(self, metrics: List[Metric]) -> Decimal:
        """Calculate total cost - REFACTORED: Complexity 3"""
        calculators = {
            'sqs': self._calculate_sqs_costs,
            'lambda': self._calculate_lambda_costs,
            'bedrock': self._calculate_bedrock_costs,
            'safety': self._calculate_safety_costs,
        }
        
        total = Decimal('0')
        for metric_type, calculator in calculators.items():
            type_metrics = [m for m in metrics if m.metric_type == metric_type]
            if type_metrics:
                total += calculator(type_metrics)
        
        return total
```

#### Maintainability Index

```bash
# Generate maintainability report
radon mi src/ -s

# Files below B grade (should be A)
```

| File | MI Score | Rank | Issues |
|------|----------|------|--------|
| `ingest_service.py` | 65.2 | C | Complex logic |
| `report_service.py` | 68.5 | B | Long functions |
| `scenario.py` (routes) | 72.1 | B | Multiple concerns |

#### Raw Metrics

```bash
radon raw src/

# Code Statistics:
# - Total LOC: ~5,800
# - Source LOC: ~4,200
# - Comment LOC: ~800 (19% - GOOD)
# - Blank LOC: ~800
# - Functions: ~150
# - Classes: ~25
```

### 1.2 Code Duplication Analysis

#### Duplicated Code Blocks

```bash
# Using jscpd or similar
jscpd src/ --reporters console,html --output reports/
```

**Found Duplications:**

| Location 1 | Location 2 | Lines | Similarity | Priority |
|------------|------------|-------|------------|----------|
| `auth.py:45-62` | `apikeys.py:38-55` | 18 | 85% | HIGH |
| `scenario.py:98-115` | `scenario.py:133-150` | 18 | 90% | MEDIUM |
| `ingest.py:25-42` | `metrics.py:30-47` | 18 | 75% | MEDIUM |
| `user.py:25-40` | `auth_service.py:45-60` | 16 | 80% | HIGH |

**Example - Authentication Check Duplication:**

```python
# DUPLICATE in src/api/v1/auth.py:45-62
@router.post("/login")
async def login(credentials: LoginRequest, db: AsyncSession = Depends(get_db)):
    user = await user_repository.get_by_email(db, credentials.email)
    if not user:
        raise HTTPException(status_code=401, detail="Invalid credentials")
    
    if not verify_password(credentials.password, user.password_hash):
        raise HTTPException(status_code=401, detail="Invalid credentials")
    
    if not user.is_active:
        raise HTTPException(status_code=401, detail="User is inactive")
    
    # ... continue

# DUPLICATE in src/api/v1/apikeys.py:38-55
@router.post("/verify")
async def verify_api_key(key: str, db: AsyncSession = Depends(get_db)):
    api_key = await apikey_repository.get_by_prefix(db, key[:8])
    if not api_key:
        raise HTTPException(status_code=401, detail="Invalid API key")
    
    if not verify_api_key_hash(key, api_key.key_hash):
        raise HTTPException(status_code=401, detail="Invalid API key")
    
    if not api_key.is_active:
        raise HTTPException(status_code=401, detail="API key is inactive")
    
    # ... continue

# REFACTORED - Extract to decorator
from functools import wraps

def require_active_entity(entity_type: str):
    """Decorator to check entity is active."""
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            entity = await func(*args, **kwargs)
            if not entity:
                raise HTTPException(status_code=401, detail=f"Invalid {entity_type}")
            if not entity.is_active:
                raise HTTPException(status_code=401, detail=f"{entity_type} is inactive")
            return entity
        return wrapper
    return decorator
```

### 1.3 N+1 Query Detection

#### Identified N+1 Issues

```python
# ISSUE: src/api/v1/scenarios.py:37-65
@router.get("", response_model=ScenarioList)
async def list_scenarios(
    status: str = Query(None),
    page: int = Query(1),
    db: AsyncSession = Depends(get_db),
):
    """List scenarios - N+1 PROBLEM"""
    skip = (page - 1) * 20
    scenarios = await scenario_repository.get_multi(db, skip=skip, limit=20)
    
    # N+1: Each scenario triggers a separate query for logs count
    result = []
    for scenario in scenarios:
        logs_count = await log_repository.count_by_scenario(db, scenario.id)  # N queries!
        result.append({
            **scenario.to_dict(),
            "logs_count": logs_count
        })
    
    return result

# TOTAL QUERIES: 1 (scenarios) + N (logs count) = N+1

# REFACTORED - Eager loading
from sqlalchemy.orm import selectinload

@router.get("", response_model=ScenarioList)
async def list_scenarios(
    status: str = Query(None),
    page: int = Query(1),
    db: AsyncSession = Depends(get_db),
):
    """List scenarios - FIXED with eager loading"""
    skip = (page - 1) * 20
    
    query = (
        select(Scenario)
        .options(
            selectinload(Scenario.logs),  # Load all logs in one query
            selectinload(Scenario.metrics)  # Load all metrics in one query
        )
        .offset(skip)
        .limit(20)
    )
    
    if status:
        query = query.where(Scenario.status == status)
    
    result = await db.execute(query)
    scenarios = result.scalars().all()
    
    # logs and metrics are already loaded - no additional queries!
    return [{
        **scenario.to_dict(),
        "logs_count": len(scenario.logs)
    } for scenario in scenarios]

# TOTAL QUERIES: 3 (scenarios + logs + metrics) regardless of N
```

**N+1 Query Summary:**

| Location | Issue | Impact | Fix Strategy |
|----------|-------|--------|--------------|
| `scenarios.py:37` | Logs count per scenario | HIGH | Eager loading |
| `scenarios.py:67` | Metrics per scenario | HIGH | Eager loading |
| `reports.py:45` | User details per report | MEDIUM | Join query |
| `metrics.py:30` | Scenario lookup per metric | MEDIUM | Bulk fetch |

### 1.4 Error Handling Coverage

#### Exception Handler Analysis

```python
# src/core/exceptions.py - Current coverage

class AppException(Exception):
    """Base exception - GOOD"""
    status_code: int = 500
    code: str = "internal_error"

class NotFoundException(AppException):
    """404 - GOOD"""
    status_code = 404
    code = "not_found"

class ValidationException(AppException):
    """400 - GOOD"""
    status_code = 400
    code = "validation_error"

class ConflictException(AppException):
    """409 - GOOD"""
    status_code = 409
    code = "conflict"

# MISSING EXCEPTIONS:
# - UnauthorizedException (401)
# - ForbiddenException (403)
# - RateLimitException (429)
# - ServiceUnavailableException (503)
# - BadGatewayException (502)
# - GatewayTimeoutException (504)
# - DatabaseException (500)
# - ExternalServiceException (502/504)
```

**Gaps in Error Handling:**

| Scenario | Current | Expected | Gap |
|----------|---------|----------|-----|
| Invalid JWT | Generic 500 | 401 with code | HIGH |
| Expired token | Generic 500 | 401 with code | HIGH |
| Rate limited | Generic 500 | 429 with retry-after | HIGH |
| DB connection lost | Generic 500 | 503 with retry | MEDIUM |
| External API timeout | Generic 500 | 504 with context | MEDIUM |
| Validation errors | 400 basic | 400 with field details | MEDIUM |

#### Proposed Error Structure

```python
# src/core/exceptions.py - Enhanced

class UnauthorizedException(AppException):
    """401 - Authentication required"""
    status_code = 401
    code = "unauthorized"

class ForbiddenException(AppException):
    """403 - Insufficient permissions"""
    status_code = 403
    code = "forbidden"
    
    def __init__(self, resource: str = None, action: str = None):
        message = f"Not authorized to {action} {resource}" if resource and action else "Forbidden"
        super().__init__(message)

class RateLimitException(AppException):
    """429 - Too many requests"""
    status_code = 429
    code = "rate_limited"
    
    def __init__(self, retry_after: int = 60):
        super().__init__(f"Rate limit exceeded. Retry after {retry_after} seconds.")
        self.retry_after = retry_after

class DatabaseException(AppException):
    """500 - Database error"""
    status_code = 500
    code = "database_error"
    
    def __init__(self, operation: str = None):
        message = f"Database error during {operation}" if operation else "Database error"
        super().__init__(message)

class ExternalServiceException(AppException):
    """502/504 - External service error"""
    status_code = 502
    code = "external_service_error"
    
    def __init__(self, service: str = None, original_error: str = None):
        message = f"Error calling {service}" if service else "External service error"
        if original_error:
            message += f": {original_error}"
        super().__init__(message)


# Enhanced exception handler
def setup_exception_handlers(app):
    @app.exception_handler(AppException)
    async def app_exception_handler(request: Request, exc: AppException):
        response = {
            "error": exc.code,
            "message": exc.message,
            "status_code": exc.status_code,
            "timestamp": datetime.utcnow().isoformat(),
            "path": str(request.url),
        }
        
        headers = {}
        if isinstance(exc, RateLimitException):
            headers["Retry-After"] = str(exc.retry_after)
            headers["X-RateLimit-Limit"] = "100"
            headers["X-RateLimit-Remaining"] = "0"
        
        return JSONResponse(
            status_code=exc.status_code,
            content=response,
            headers=headers
        )
```

---

## 2. Test Coverage Analysis

### 2.1 Current Test Coverage

```bash
# Run coverage report
pytest --cov=src --cov-report=html --cov-report=term-missing

# Current coverage summary:
# Module              Statements  Missing  Coverage
# ------------------  ----------  -------  --------
# src/core/           245         98       60%
# src/api/            380         220      42%
# src/services/       520         310      40%
# src/repositories/   180         45       75%
# src/models/         120         10       92%
# ------------------  ----------  -------  --------
# TOTAL               1445        683      53%
```

**Target: 80% coverage for v1.0.0**

### 2.2 Coverage Gaps

#### Critical Path Gaps

| Module | Current | Target | Missing Tests |
|--------|---------|--------|---------------|
| `auth_service.py` | 35% | 90% | Token refresh, password reset |
| `ingest_service.py` | 40% | 85% | Concurrent ingestion, error handling |
| `cost_calculator.py` | 30% | 85% | Edge cases, all pricing tiers |
| `report_service.py` | 25% | 80% | PDF generation, large reports |
| `apikeys.py` (routes) | 45% | 85% | Scope validation, revocation |

#### Missing Test Types

```python
# MISSING: Integration tests for database transactions
async def test_scenario_creation_rollback_on_error():
    """Test that scenario creation rolls back on subsequent error."""
    pass

# MISSING: Concurrent request tests
async def test_concurrent_scenario_updates():
    """Test race condition handling in scenario updates."""
    pass

# MISSING: Load tests for critical paths
async def test_ingest_under_load():
    """Test log ingestion under high load."""
    pass

# MISSING: Security-focused tests
async def test_sql_injection_attempts():
    """Test parameterized queries prevent injection."""
    pass

async def test_authentication_bypass_attempts():
    """Test authentication cannot be bypassed."""
    pass

# MISSING: Error handling tests
async def test_graceful_degradation_on_db_failure():
    """Test system behavior when DB is unavailable."""
    pass
```

### 2.3 Test Quality Issues

| Issue | Examples | Impact | Fix |
|-------|----------|--------|-----|
| Hardcoded IDs | `scenario_id = "abc-123"` | Fragile | Use fixtures |
| No setup/teardown | Tests leak data | Instability | Proper cleanup |
| Mock overuse | Mock entire service | Low confidence | Integration tests |
| Missing assertions | Only check status code | Low value | Assert response |
| Test duplication | Same test 3x | Maintenance | Parameterize |

---

## 3. Architecture Debt

### 3.1 Architectural Issues

#### Service Layer Concerns

```python
# ISSUE: src/services/ingest_service.py
# Service is doing too much - violates Single Responsibility

class IngestService:
    def ingest_log(self, db, scenario, message, source):
        # 1. Validation
        # 2. PII Detection (should be separate service)
        # 3. Token Counting (should be utility)
        # 4. SQS Block Calculation (should be utility)
        # 5. Hash Calculation (should be utility)
        # 6. Database Write
        # 7. Metrics Update
        # 8. Cache Invalidation
        pass

# REFACTORED - Separate concerns
class LogNormalizer:
    def normalize(self, message: str) -> NormalizedLog:
        pass

class PIIDetector:
    def detect(self, message: str) -> PIIScanResult:
        pass

class TokenCounter:
    def count(self, message: str) -> int:
        pass

class IngestService:
    def __init__(self, normalizer, pii_detector, token_counter):
        self.normalizer = normalizer
        self.pii_detector = pii_detector
        self.token_counter = token_counter
    
    async def ingest_log(self, db, scenario, message, source):
        # Orchestrate, don't implement
        normalized = self.normalizer.normalize(message)
        pii_result = self.pii_detector.detect(message)
        token_count = self.token_counter.count(message)
        # ... persist
```

#### Repository Pattern Issues

```python
# ISSUE: src/repositories/base.py
# Generic repository too generic - loses type safety

class BaseRepository(Generic[ModelType]):
    async def get_multi(self, db, skip=0, limit=100, **filters):
        # **filters is not type-safe
        # No IDE completion
        # Runtime errors possible
        pass

# REFACTORED - Type-safe specific repositories
from typing import TypedDict, Unpack

class ScenarioFilters(TypedDict, total=False):
    status: str
    region: str
    created_after: datetime
    created_before: datetime

class ScenarioRepository:
    async def list(
        self, 
        db: AsyncSession, 
        skip: int = 0, 
        limit: int = 100,
        **filters: Unpack[ScenarioFilters]
    ) -> List[Scenario]:
        # Type-safe, IDE completion, validated
        pass
```

### 3.2 Configuration Management

#### Current Issues

```python
# src/core/config.py - ISSUES:
# 1. No validation of critical settings
# 2. Secrets in plain text (acceptable for env vars but should be marked)
# 3. No environment-specific overrides
# 4. Missing documentation

class Settings(BaseSettings):
    # No validation - could be empty string
    jwt_secret_key: str = "default-secret"  # DANGEROUS default
    
    # No range validation
    access_token_expire_minutes: int = 30  # Could be negative!
    
    # No URL validation
    database_url: str = "..."

# REFACTORED - Validated configuration
from pydantic import Field, HttpUrl, validator

class Settings(BaseSettings):
    # Validated secret with no default
    jwt_secret_key: str = Field(
        ...,  # Required - no default!
        min_length=32,
        description="JWT signing secret (min 256 bits)"
    )
    
    # Validated range
    access_token_expire_minutes: int = Field(
        default=30,
        ge=5,  # Minimum 5 minutes
        le=1440,  # Maximum 24 hours
        description="Access token expiration time"
    )
    
    # Validated URL
    database_url: str = Field(
        ...,
        regex=r"^postgresql\+asyncpg://.*",
        description="PostgreSQL connection URL"
    )
    
    @validator('jwt_secret_key')
    def validate_not_default(cls, v):
        if v == "default-secret":
            raise ValueError("JWT secret must be changed from default")
        return v
```

### 3.3 Monitoring and Observability Gaps

| Area | Current | Required | Gap |
|------|---------|----------|-----|
| Structured logging | Basic | JSON, correlation IDs | HIGH |
| Metrics (Prometheus) | None | Full instrumentation | HIGH |
| Distributed tracing | None | OpenTelemetry | MEDIUM |
| Health checks | Basic | Deep health checks | MEDIUM |
| Alerting | None | PagerDuty integration | HIGH |

---

## 4. Documentation Debt

### 4.1 API Documentation Gaps

```python
# Current: Missing examples and detailed schemas
@router.post("/scenarios")
async def create_scenario(scenario_in: ScenarioCreate):
    """Create a scenario."""  # Too brief!
    pass

# Required: Comprehensive OpenAPI documentation
@router.post(
    "/scenarios",
    response_model=ScenarioResponse,
    status_code=201,
    summary="Create a new scenario",
    description="""
    Create a new cost simulation scenario.
    
    The scenario starts in 'draft' status and must be started
    before log ingestion can begin.
    
    **Required Permissions:** write:scenarios
    
    **Rate Limit:** 100/minute
    """,
    responses={
        201: {
            "description": "Scenario created successfully",
            "content": {
                "application/json": {
                    "example": {
                        "id": "550e8400-e29b-41d4-a716-446655440000",
                        "name": "Production Load Test",
                        "status": "draft",
                        "created_at": "2026-04-07T12:00:00Z"
                    }
                }
            }
        },
        400: {"description": "Validation error"},
        401: {"description": "Authentication required"},
        429: {"description": "Rate limit exceeded"}
    }
)
async def create_scenario(scenario_in: ScenarioCreate):
    pass
```

### 4.2 Missing Documentation

| Document | Purpose | Priority |
|----------|---------|----------|
| API Reference | Complete OpenAPI spec | HIGH |
| Architecture Decision Records | Why decisions were made | MEDIUM |
| Runbooks | Operational procedures | HIGH |
| Onboarding Guide | New developer setup | MEDIUM |
| Troubleshooting Guide | Common issues | MEDIUM |
| Performance Tuning | Optimization guide | LOW |

---

## 5. Refactoring Priority List

### 5.1 Priority Matrix

```
                    High Impact
                         │
        ┌────────────────┼────────────────┐
        │                │                │
        │  P0 - Do First │  P1 - Critical │
        │                │                │
        │ • N+1 queries  │ • Complex code │
        │ • Error handling│  refactoring  │
        │ • Security gaps│ • Test coverage│
        │ • Config val.  │                │
        │                │                │
────────┼────────────────┼────────────────┼────────
        │                │                │
        │  P2 - Should   │  P3 - Could    │
        │                │                │
        │ • Code dup.    │ • Documentation│
        │ • Monitoring   │ • Logging      │
        │ • Repository   │ • Comments     │
        │   pattern      │                │
        │                │                │
        └────────────────┼────────────────┘
                         │
                    Low Impact
        Low Effort                         High Effort
```

### 5.2 Detailed Refactoring Plan

#### P0 - Critical (Week 1)

| # | Task | Effort | Owner | Acceptance Criteria |
|---|------|--------|-------|---------------------|
| P0-1 | Fix N+1 queries in scenarios list | 4h | Backend | 3 queries max regardless of page size |
| P0-2 | Implement missing exception types | 3h | Backend | All HTTP status codes have specific exception |
| P0-3 | Add JWT secret validation | 2h | Backend | Reject default/changed secrets |
| P0-4 | Add rate limiting middleware | 6h | Backend | 429 responses with proper headers |
| P0-5 | Fix authentication bypass risks | 4h | Backend | Security team sign-off |

#### P1 - High Priority (Week 2)

| # | Task | Effort | Owner | Acceptance Criteria |
|---|------|--------|-------|---------------------|
| P1-1 | Refactor high-complexity functions | 8h | Backend | Complexity < 8 per function |
| P1-2 | Extract duplicate auth code | 4h | Backend | Zero duplication in auth flow |
| P1-3 | Add integration tests (auth) | 6h | QA | 90% coverage on auth flows |
| P1-4 | Add integration tests (ingest) | 6h | QA | 85% coverage on ingest |
| P1-5 | Implement structured logging | 6h | Backend | JSON logs with correlation IDs |

#### P2 - Medium Priority (Week 3)

| # | Task | Effort | Owner | Acceptance Criteria |
|---|------|--------|-------|---------------------|
| P2-1 | Extract service layer concerns | 8h | Backend | Single responsibility per service |
| P2-2 | Add Prometheus metrics | 6h | Backend | Key metrics exposed on /metrics |
| P2-3 | Add deep health checks | 4h | Backend | /health/db checks connectivity |
| P2-4 | Improve API documentation | 6h | Backend | All endpoints have examples |
| P2-5 | Add type hints to repositories | 4h | Backend | Full mypy coverage |

#### P3 - Low Priority (Week 4)

| # | Task | Effort | Owner | Acceptance Criteria |
|---|------|--------|-------|---------------------|
| P3-1 | Write runbooks | 8h | DevOps | 5 critical runbooks complete |
| P3-2 | Add ADR documents | 4h | Architect | Key decisions documented |
| P3-3 | Improve inline comments | 4h | Backend | Complex logic documented |
| P3-4 | Add performance tests | 6h | QA | Baseline benchmarks established |
| P3-5 | Code style consistency | 4h | Backend | Ruff/pylint clean |

### 5.3 Effort Estimates Summary

| Priority | Tasks | Total Effort | Team |
|----------|-------|--------------|------|
| P0 | 5 | 19h (~3 days) | Backend |
| P1 | 5 | 30h (~4 days) | Backend + QA |
| P2 | 5 | 28h (~4 days) | Backend |
| P3 | 5 | 26h (~4 days) | All |
| **Total** | **20** | **103h (~15 days)** | - |

---

## 6. Remediation Strategy

### 6.1 Immediate Actions (This Week)

1. **Create refactoring branches**
   ```bash
   git checkout -b refactor/p0-error-handling
   git checkout -b refactor/p0-n-plus-one
   ```

2. **Set up code quality gates**
   ```yaml
   # .github/workflows/quality.yml
   - name: Complexity Check
     run: |
       pip install radon
       radon cc src/ -nc --min=C
   
   - name: Test Coverage
     run: |
       pytest --cov=src --cov-fail-under=80
   ```

3. **Schedule refactoring sprints**
   - Sprint 1: P0 items (Week 1)
   - Sprint 2: P1 items (Week 2)
   - Sprint 3: P2 items (Week 3)
   - Sprint 4: P3 items + buffer (Week 4)

### 6.2 Long-term Prevention

```
Pre-commit Hooks:
├── radon cc --min=B (prevent high complexity)
├── bandit -ll (security scan)
├── mypy --strict (type checking)
├── pytest --cov-fail-under=80 (coverage)
└── ruff check (linting)

CI/CD Gates:
├── Complexity < 10 per function
├── Test coverage >= 80%
├── No high-severity CVEs
├── Security scan clean
└── Type checking passes

Code Review Checklist:
□ No N+1 queries
□ Proper error handling
□ Type hints present
□ Tests included
□ Documentation updated
```

### 6.3 Success Metrics

| Metric | Current | Target | Measurement |
|--------|---------|--------|-------------|
| Test Coverage | 53% | 80% | pytest-cov |
| Complexity (avg) | 4.5 | <3.5 | radon |
| Max Complexity | 15 | <8 | radon |
| Code Duplication | 8 blocks | 0 blocks | jscpd |
| MyPy Errors | 45 | 0 | mypy |
| Bandit Issues | 12 | 0 | bandit |

---

## Appendix A: Code Quality Scripts

### Automated Quality Checks

```bash
#!/bin/bash
# scripts/quality-check.sh

echo "=== Running Code Quality Checks ==="

# 1. Cyclomatic complexity
echo "Checking complexity..."
radon cc src/ -a -nc --min=C || exit 1

# 2. Maintainability index
echo "Checking maintainability..."
radon mi src/ -s --min=B || exit 1

# 3. Security scan
echo "Security scanning..."
bandit -r src/ -ll || exit 1

# 4. Type checking
echo "Type checking..."
mypy src/ --strict || exit 1

# 5. Test coverage
echo "Running tests with coverage..."
pytest --cov=src --cov-fail-under=80 || exit 1

# 6. Linting
echo "Linting..."
ruff check src/ || exit 1

echo "=== All Checks Passed ==="
```

### Pre-commit Configuration

```yaml
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: radon
        name: radon complexity check
        entry: radon cc
        args: [--min=C, --average]
        language: system
        files: \.py$
      
      - id: bandit
        name: bandit security check
        entry: bandit
        args: [-r, src/, -ll]
        language: system
        files: \.py$
      
      - id: pytest-cov
        name: pytest coverage
        entry: pytest
        args: [--cov=src, --cov-fail-under=80]
        language: system
        pass_filenames: false
        always_run: true
```

---

## Appendix B: Architecture Decision Records (Template)

### ADR-001: Repository Pattern Implementation

**Status:** Accepted  
**Date:** 2026-04-07

#### Context
Need for consistent data access patterns across the application.

#### Decision
Implement Generic Repository pattern with SQLAlchemy 2.0 async support.

#### Consequences
- **Positive:** Consistent API, testable, DRY
- **Negative:** Some loss of type safety with **filters
- **Mitigation:** Create typed filters per repository

#### Alternatives
- **Active Record:** Rejected - too much responsibility in models
- **Query Objects:** Rejected - more complex for current needs

---

*Document Version: 1.0.0-Draft*  
*Last Updated: 2026-04-07*  
*Owner: @spec-architect*
