# Phase 5: Analytics Enhancement — Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Extend the FlowPilot Analytics page with tabbed sections for coverage heatmap, flow quality scoring, and PSA time tracking metrics.

**Architecture:** Add two new backend endpoints (`/coverage` and `/flow-quality`) alongside the existing `/analytics/flowpilot` endpoint. Add a `psa_activity_log` table for time entry tracking. Add flow usage columns to `trees`. Refactor the frontend analytics page into a tabbed layout with lazy-loaded sections.

**Tech Stack:** FastAPI, SQLAlchemy 2.0 (async), React 19, TypeScript, Tailwind CSS v4, Recharts

**Design doc:** `docs/plans/2026-03-19-phase5-analytics-enhancement-design.md`

---

## Task 1: Database — add flow tracking columns + PSA activity log table

**Files:**
- Modify: `backend/app/models/tree.py`
- Create: `backend/app/models/psa_activity_log.py`
- Create: migration file

**Step 1: Add flow tracking columns to Tree model**

In `backend/app/models/tree.py`, add after `gallery_sort_order`:

```python
# Flow quality tracking (Phase 5)
usage_count: Mapped[int] = mapped_column(Integer, default=0)
success_rate: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
last_matched_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
```

Import `Float` from sqlalchemy if not already imported.

**Step 2: Create PSA activity log model**

Create `backend/app/models/psa_activity_log.py`:

```python
"""PSA activity log — tracks time entries, note posts, and status updates pushed to PSA."""
import uuid
from datetime import datetime, timezone
from typing import Optional

from sqlalchemy import String, DateTime, ForeignKey, Float
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.dialects.postgresql import UUID

from app.core.database import Base


class PsaActivityLog(Base):
    __tablename__ = "psa_activity_logs"

    id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    account_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True), ForeignKey("accounts.id", ondelete="CASCADE"), nullable=False, index=True
    )
    session_id: Mapped[Optional[uuid.UUID]] = mapped_column(
        UUID(as_uuid=True), ForeignKey("ai_sessions.id", ondelete="SET NULL"), nullable=True
    )
    activity_type: Mapped[str] = mapped_column(String(50), nullable=False)  # "time_entry_posted", "note_posted", "status_updated"
    hours_logged: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
    psa_ticket_id: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
    )
```

**Step 3: Register model in imports**

Ensure the model is importable. Check how other models are registered (e.g., in `backend/app/models/__init__.py` if it exists, or via alembic's `env.py`).

**Step 4: Generate and run migration**

```bash
cd /projects/patherly/backend && alembic revision --autogenerate -m "add flow tracking columns and psa_activity_logs table"
alembic upgrade head
```

**Step 5: Commit**

```bash
git commit -m "feat(analytics): add flow tracking columns and psa_activity_logs table"
```

---

## Task 2: Backend — coverage endpoint

**Files:**
- Modify: `backend/app/api/endpoints/flowpilot_analytics.py`
- Modify: `backend/app/schemas/flowpilot_analytics.py`
- Create: `backend/tests/test_analytics_coverage.py`

**Step 1: Add schemas**

In `backend/app/schemas/flowpilot_analytics.py`, add:

```python
class CoverageDomainRow(BaseModel):
    domain: str
    flow_count: int
    session_count: int
    resolution_rate: float
    escalation_rate: float
    guided_rate: float
    avg_resolution_minutes: float | None = None


class CoverageResponse(BaseModel):
    domains: list[CoverageDomainRow]
    unmapped_session_count: int
    total_domains: int
```

**Step 2: Add endpoint**

In `backend/app/api/endpoints/flowpilot_analytics.py`, add:

```python
@router.get("/coverage", response_model=CoverageResponse)
@limiter.limit("15/minute")
async def get_coverage(
    request: Request,
    current_user: Annotated[User, Depends(get_current_active_user)],
    db: Annotated[AsyncSession, Depends(get_db)],
    _: None = Depends(require_team_admin),
    period: str = Query("30d", pattern="^(7d|30d|90d)$"),
):
    """Coverage heatmap data — per-domain metrics for flow coverage analysis."""
    if not current_user.account_id:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="No account")

    account_id = current_user.account_id
    period_start = _get_period_start(period)

    # Get all domains from sessions in period
    domain_stats = await db.execute(
        select(
            AISession.problem_domain,
            func.count(AISession.id).label("session_count"),
            func.sum(case((AISession.status == "resolved", 1), else_=0)).label("resolved"),
            func.sum(case((AISession.status == "escalated", 1), else_=0)).label("escalated"),
            func.sum(case((AISession.confidence_tier == "guided", 1), else_=0)).label("guided"),
        )
        .where(
            AISession.account_id == account_id,
            AISession.created_at >= period_start,
            AISession.problem_domain.isnot(None),
        )
        .group_by(AISession.problem_domain)
    )
    domain_rows = domain_rows_result = domain_stats.all()

    # Count flows per domain via category name matching
    # Trees have category_id → Category.name, sessions have problem_domain
    # Match by comparing Category.name to problem_domain
    from app.models.category import Category
    flow_counts_result = await db.execute(
        select(Category.name, func.count(Tree.id))
        .join(Tree, Tree.category_id == Category.id)
        .where(Tree.account_id == account_id, Tree.is_active == True)
        .group_by(Category.name)
    )
    flow_counts = {row[0]: row[1] for row in flow_counts_result.all()}

    # Count unmapped sessions (no problem_domain)
    unmapped_result = await db.execute(
        select(func.count(AISession.id))
        .where(
            AISession.account_id == account_id,
            AISession.created_at >= period_start,
            AISession.problem_domain.is_(None),
        )
    )
    unmapped_count = unmapped_result.scalar() or 0

    # Build response
    domains = []
    for row in domain_rows:
        total = int(row.session_count)
        resolved = int(row.resolved)
        escalated = int(row.escalated)
        guided = int(row.guided)
        domain_name = row.problem_domain

        domains.append(CoverageDomainRow(
            domain=domain_name,
            flow_count=flow_counts.get(domain_name, 0),
            session_count=total,
            resolution_rate=round(resolved / total, 3) if total else 0,
            escalation_rate=round(escalated / total, 3) if total else 0,
            guided_rate=round(guided / total, 3) if total else 0,
        ))

    # Sort by session count descending
    domains.sort(key=lambda d: d.session_count, reverse=True)

    return CoverageResponse(
        domains=domains,
        unmapped_session_count=unmapped_count,
        total_domains=len(domains),
    )
```

**Step 3: Write tests**

Create `backend/tests/test_analytics_coverage.py` testing:
- Endpoint requires team_admin auth
- Returns domain breakdown with correct counts
- Unmapped sessions counted
- Empty state returns empty list

**Step 4: Run tests and verify**

```bash
cd /projects/patherly/backend && python -m pytest tests/test_analytics_coverage.py -v --override-ini="addopts="
```

**Step 5: Commit**

```bash
git commit -m "feat(analytics): add coverage heatmap endpoint"
```

---

## Task 3: Backend — flow quality endpoint

**Files:**
- Modify: `backend/app/api/endpoints/flowpilot_analytics.py`
- Modify: `backend/app/schemas/flowpilot_analytics.py`
- Create: `backend/tests/test_analytics_flow_quality.py`

**Step 1: Add schemas**

```python
class FlowQualityRow(BaseModel):
    flow_id: str
    name: str
    tree_type: str
    usage_count: int
    success_rate: float | None = None
    last_matched_at: datetime | None = None
    avg_confidence: float | None = None
    quality_score: float


class FlowQualityResponse(BaseModel):
    flows: list[FlowQualityRow]
    top_performers: list[FlowQualityRow]
    needs_attention: list[FlowQualityRow]
```

**Step 2: Add endpoint**

```python
@router.get("/flow-quality", response_model=FlowQualityResponse)
@limiter.limit("15/minute")
async def get_flow_quality(
    request: Request,
    current_user: Annotated[User, Depends(get_current_active_user)],
    db: Annotated[AsyncSession, Depends(get_db)],
    _: None = Depends(require_team_admin),
    period: str = Query("30d", pattern="^(7d|30d|90d)$"),
    sort: str = Query("quality", pattern="^(quality|usage|success_rate)$"),
):
```

Query logic:
- Get all active flows for the account
- For each flow, count sessions where `matched_flow_id == flow.id` in period
- Calculate success_rate = resolved / total matched
- Calculate quality_score = `(success_rate * 0.5) + (guided_rate * 0.3) + (recency_score * 0.2)`
- Recency score: 1.0 if used today, decays linearly to 0.0 at 90 days
- Top performers: top 5 by quality_score
- Needs attention: flows with success_rate < 0.5 or not used in 30+ days

**Step 3: Write tests, run, commit**

```bash
git commit -m "feat(analytics): add flow quality scoring endpoint"
```

---

## Task 4: Backend — PSA activity logging + enhanced PSA metrics

**Files:**
- Modify: `backend/app/services/psa/connectwise/provider.py` (or wherever note/time entry posting happens)
- Modify: `backend/app/api/endpoints/flowpilot_analytics.py`
- Modify: `backend/app/schemas/flowpilot_analytics.py`

**Step 1: Add PSA activity logging**

Find where the ConnectWise provider posts notes and time entries. After a successful push, log to `psa_activity_logs`:

```python
from app.models.psa_activity_log import PsaActivityLog

activity = PsaActivityLog(
    account_id=account_id,
    session_id=session_id,
    activity_type="note_posted",  # or "time_entry_posted"
    hours_logged=hours,  # for time entries
    psa_ticket_id=ticket_id,
)
db.add(activity)
await db.commit()
```

**Step 2: Add enhanced PSA schemas**

```python
class PsaFunnel(BaseModel):
    total_sessions: int
    linked_to_ticket: int
    doc_pushed: int
    time_entry_logged: int


class PsaDailyTrend(BaseModel):
    date: str
    entries: int
    hours: float


class EnhancedPsaMetrics(BaseModel):
    total_time_entries: int
    total_hours_logged: float
    avg_hours_per_session: float
    push_funnel: PsaFunnel
    daily_trend: list[PsaDailyTrend]
```

**Step 3: Add PSA metrics endpoint**

```python
@router.get("/psa-metrics", response_model=EnhancedPsaMetrics)
```

Query `psa_activity_logs` and `ai_sessions` to build the funnel and trend data.

**Step 4: Write tests, run, commit**

```bash
git commit -m "feat(analytics): add PSA activity logging and enhanced PSA metrics endpoint"
```

---

## Task 5: Backend — wire flow matching stats

**Files:**
- Modify: `backend/app/services/flowpilot_engine.py` (or wherever flow matching happens)

**Step 1: Update flow stats on match**

Find where `matched_flow_id` is set on an `AISession`. At that point, also update the matched flow:

```python
# When a flow is matched to a session:
flow.usage_count = (flow.usage_count or 0) + 1
flow.last_matched_at = datetime.now(timezone.utc)
```

**Step 2: Update success_rate on resolution**

When a session resolves and has a `matched_flow_id`, recalculate that flow's success_rate:

```python
# After session resolves:
if session.matched_flow_id:
    total = await db.execute(
        select(func.count(AISession.id))
        .where(AISession.matched_flow_id == session.matched_flow_id)
    )
    resolved = await db.execute(
        select(func.count(AISession.id))
        .where(AISession.matched_flow_id == session.matched_flow_id, AISession.status == "resolved")
    )
    flow.success_rate = round(resolved.scalar() / total.scalar(), 3) if total.scalar() else None
```

**Step 3: Commit**

```bash
git commit -m "feat(analytics): wire flow usage tracking into session matching and resolution"
```

---

## Task 6: Frontend — types and API client updates

**Files:**
- Modify: `frontend/src/types/flowpilot-analytics.ts`
- Modify: `frontend/src/api/flowpilotAnalytics.ts`

**Step 1: Add types**

```typescript
// Coverage
export interface CoverageDomainRow {
  domain: string
  flow_count: number
  session_count: number
  resolution_rate: number
  escalation_rate: number
  guided_rate: number
  avg_resolution_minutes: number | null
}

export interface CoverageResponse {
  domains: CoverageDomainRow[]
  unmapped_session_count: number
  total_domains: number
}

// Flow Quality
export interface FlowQualityRow {
  flow_id: string
  name: string
  tree_type: string
  usage_count: number
  success_rate: number | null
  last_matched_at: string | null
  avg_confidence: number | null
  quality_score: number
}

export interface FlowQualityResponse {
  flows: FlowQualityRow[]
  top_performers: FlowQualityRow[]
  needs_attention: FlowQualityRow[]
}

// Enhanced PSA
export interface PsaFunnel {
  total_sessions: number
  linked_to_ticket: number
  doc_pushed: number
  time_entry_logged: number
}

export interface PsaDailyTrend {
  date: string
  entries: number
  hours: number
}

export interface EnhancedPsaMetrics {
  total_time_entries: number
  total_hours_logged: number
  avg_hours_per_session: number
  push_funnel: PsaFunnel
  daily_trend: PsaDailyTrend[]
}
```

**Step 2: Add API methods**

```typescript
async getCoverage(period: string = '30d'): Promise<CoverageResponse> {
  const response = await apiClient.get<CoverageResponse>('/analytics/flowpilot/coverage', { params: { period } })
  return response.data
},

async getFlowQuality(period: string = '30d', sort: string = 'quality'): Promise<FlowQualityResponse> {
  const response = await apiClient.get<FlowQualityResponse>('/analytics/flowpilot/flow-quality', { params: { period, sort } })
  return response.data
},

async getPsaMetrics(period: string = '30d'): Promise<EnhancedPsaMetrics> {
  const response = await apiClient.get<EnhancedPsaMetrics>('/analytics/flowpilot/psa-metrics', { params: { period } })
  return response.data
},
```

**Step 3: Run build, commit**

```bash
cd /projects/patherly/frontend && npm run build
git commit -m "feat(analytics): add coverage, flow quality, and PSA metrics types and API client"
```

---

## Task 7: Frontend — tabbed layout + Coverage heatmap tab

**Files:**
- Modify: `frontend/src/pages/FlowPilotAnalyticsPage.tsx`
- Create: `frontend/src/components/analytics/CoverageHeatmap.tsx`

**Step 1: Refactor page into tabs**

Add tab state and tab bar to `FlowPilotAnalyticsPage.tsx`:
- Tabs: "Overview", "Coverage", "Flow Quality", "PSA"
- Active tab: `bg-primary/10 text-foreground border-b-2 border-primary`
- Inactive: `text-muted-foreground hover:text-foreground`
- Move existing dashboard content into the Overview tab
- Each non-overview tab fetches its data lazily on first selection

**Step 2: Build CoverageHeatmap component**

`frontend/src/components/analytics/CoverageHeatmap.tsx`:
- `.glass-card-static` table container
- Table headers: Domain, Flows, Sessions, Resolution %, Escalation %, Guided %
- Cell coloring functions:
  - Resolution: `bg-emerald-400/10 text-emerald-400` (>75%), `bg-amber-400/10 text-amber-400` (50-75%), `bg-rose-500/10 text-rose-500` (<50%)
  - Escalation: green (<10%), amber (10-25%), red (>25%)
  - Guided: green (>60%), amber (30-60%), red (<30%)
  - Flows: green (5+), amber (1-4), red (0)
- Domains with 0 flows show "Create Flow" link
- Responsive: horizontal scroll on mobile (`overflow-x-auto`)

**Step 3: Run build, commit**

```bash
git commit -m "feat(analytics): add tabbed layout and coverage heatmap"
```

---

## Task 8: Frontend — Flow Quality tab

**Files:**
- Create: `frontend/src/components/analytics/FlowQualityTable.tsx`
- Modify: `frontend/src/pages/FlowPilotAnalyticsPage.tsx`

**Step 1: Build FlowQualityTable component**

- `.glass-card-static` sortable table
- Columns: Flow Name (link to editor), Usage, Success Rate, Last Used, Avg Confidence, Quality Score
- Column headers clickable to sort
- Top 5 rows: left border `border-l-2 border-emerald-400`
- Bottom 5 rows: left border `border-l-2 border-rose-500`
- "Needs attention" badge (`bg-amber-400/10 text-amber-400 font-label text-[0.625rem]`) on flows with success_rate < 50% or unused 30+ days
- Quality score displayed as a colored bar (0-100% width, emerald/amber/rose)
- Click flow name → navigate to `/trees/{id}/edit`

**Step 2: Wire into tab, run build, commit**

```bash
git commit -m "feat(analytics): add flow quality scoring table"
```

---

## Task 9: Frontend — PSA metrics tab

**Files:**
- Create: `frontend/src/components/analytics/PsaMetricsPanel.tsx`
- Modify: `frontend/src/pages/FlowPilotAnalyticsPage.tsx`

**Step 1: Build PsaMetricsPanel component**

- **Metric cards row** (3 cards): Total Time Entries, Total Hours Logged, Avg Hours/Session
- **Push success funnel**: horizontal bar visualization showing conversion at each step (sessions → linked → pushed → time entry). Show counts + percentage between steps.
- **Trend chart**: Recharts `AreaChart` with dual Y-axes — entries (bars) and hours (area) over the period

**Step 2: Wire into tab, run build, commit**

```bash
git commit -m "feat(analytics): add PSA metrics panel with funnel and trend chart"
```

---

## Task 10: Final verification and docs

**Step 1: Run full backend tests**

```bash
cd /projects/patherly/backend && python -m pytest --override-ini="addopts="
```

**Step 2: Run frontend build**

```bash
cd /projects/patherly/frontend && npm run build
```

**Step 3: Update CURRENT-STATE.md**

Mark Phase 5 as complete. Update What's Next.

**Step 4: Commit**

```bash
git commit -m "docs: update CURRENT-STATE.md — Phase 5 Analytics Enhancement complete"
```
