> ## Documentation Index
> Fetch the complete documentation index at: https://docs.equa.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Finance Dashboard

> Financial overview, AR/AP aging, bank accounts, invoices, bills, reports, and scheduled CFO automation

# SPEC 009 — Finance Dashboard

| Field    | Value                                                                     |
| -------- | ------------------------------------------------------------------------- |
| Status   | DRAFT                                                                     |
| Priority | P1 — Core Product                                                         |
| Backend  | External: HH Finance Agent API (separate service, not equa-server)        |
| API      | `equa-web/src/modules/hh-finance/services/api.ts` (client-side API layer) |
| Frontend | `equa-web/src/modules/hh-finance/`                                        |

***

## 1. Feature Purpose

The Finance Dashboard provides a real-time financial overview for organizations, displaying key metrics (assets, liabilities, equity), accounts receivable/payable aging, bank account balances, open invoices and bills, P\&L reports, and scheduled CFO automation jobs. Unlike other Equa modules, this feature connects to an **external HH Finance Agent REST API** rather than the core equa-server backend. The finance agent syncs with QuickBooks Online and provides aggregated financial data.

***

## 2. Current State (Verified)

### 2.1 Architecture

The finance dashboard is a frontend-only module within equa-web that communicates with an external microservice:

```mermaid theme={null}
flowchart LR
    Browser["equa-web (Browser)"] -->|"HTTP fetch"| Agent["HH Finance Agent API"]
    Agent -->|"QuickBooks API"| QBO["QuickBooks Online"]
    Agent -->|"Scheduler"| Jobs["Scheduled CFO Jobs"]
```

* **No equa-server entities** — The finance agent is a standalone service; no tables in `schema.ts`
* **No authentication** — API calls from the frontend carry no Authorization header, API key, or token
* **No route-level permission gating** — Accessible to any authenticated organization member

### 2.2 Frontend

| Component        | Path                                                      |
| ---------------- | --------------------------------------------------------- |
| Module root      | `equa-web/src/modules/hh-finance/`                        |
| Components       | `equa-web/src/modules/hh-finance/components/`             |
| React hooks      | `equa-web/src/modules/hh-finance/hooks/useFinanceData.ts` |
| API service      | `equa-web/src/modules/hh-finance/services/api.ts`         |
| Type definitions | `equa-web/src/modules/hh-finance/types/index.ts`          |
| Formatters       | `equa-web/src/modules/hh-finance/utils/formatters.ts`     |

### 2.3 External API Configuration

```typescript theme={null}
const API_BASE_URL = process.env.REACT_APP_HH_FINANCE_API_URL || 'http://localhost:19792';
```

The base URL defaults to `http://localhost:19792` and must be set via environment variable for deployed environments.

***

## 3. Data Model (TypeScript Interfaces)

Since there are no database entities, the data model is defined entirely as TypeScript interfaces in `types/index.ts`.

### 3.1 Core Financial Types

**FinancialDashboard:**

| Field            | Type   | Description                   |
| ---------------- | ------ | ----------------------------- |
| totalAssets      | number | Sum of all asset accounts     |
| totalLiabilities | number | Sum of all liability accounts |
| totalEquity      | number | Assets minus liabilities      |
| netIncome        | number | optional, P\&L net income     |
| cashOnHand       | number | optional, liquid cash balance |

**KeyMetrics:**

| Field          | Type   | Description                                        |
| -------------- | ------ | -------------------------------------------------- |
| currentRatio   | number | Current assets / current liabilities               |
| quickRatio     | number | (Current assets - inventory) / current liabilities |
| workingCapital | number | Current assets - current liabilities               |
| daysReceivable | number | Average days to collect receivables                |
| daysPayable    | number | Average days to pay payables                       |

### 3.2 Account Types

**BankAccount:**

| Field       | Type   |
| ----------- | ------ |
| accountId   | string |
| accountName | string |
| accountType | string |
| balance     | number |

**AccountSummary:**

| Field   | Type              |
| ------- | ----------------- |
| id      | string            |
| name    | string            |
| type    | string            |
| subType | string (optional) |
| balance | number            |
| active  | boolean           |

### 3.3 Customer and Vendor Types

**CustomerSummary / VendorSummary** (same structure):

| Field       | Type              |
| ----------- | ----------------- |
| id          | string            |
| displayName | string            |
| companyName | string (optional) |
| email       | string (optional) |
| phone       | string (optional) |
| balance     | number            |
| active      | boolean           |

### 3.4 Transaction Types

**InvoiceSummary:**

| Field        | Type              | Description                                  |
| ------------ | ----------------- | -------------------------------------------- |
| id           | string            | Invoice ID                                   |
| docNumber    | string (optional) | Invoice number                               |
| date         | string            | Issue date                                   |
| dueDate      | string (optional) | Payment due date                             |
| customerName | string            | Customer display name                        |
| customerId   | string            | Customer ID                                  |
| total        | number            | Invoice total amount                         |
| balance      | number            | Outstanding balance                          |
| status       | InvoiceStatus     | `'paid' \| 'partial' \| 'open' \| 'overdue'` |

**BillSummary:**

| Field      | Type              | Description                                  |
| ---------- | ----------------- | -------------------------------------------- |
| id         | string            | Bill ID                                      |
| docNumber  | string (optional) | Bill number                                  |
| date       | string            | Issue date                                   |
| dueDate    | string (optional) | Payment due date                             |
| vendorName | string            | Vendor display name                          |
| vendorId   | string            | Vendor ID                                    |
| total      | number            | Bill total amount                            |
| balance    | number            | Outstanding balance                          |
| status     | BillStatus        | `'paid' \| 'partial' \| 'open' \| 'overdue'` |

### 3.5 Aging

**AgingBucket:**

| Field   | Type   | Description           |
| ------- | ------ | --------------------- |
| current | number | Not yet due           |
| 1-30    | number | 1–30 days past due    |
| 31-60   | number | 31–60 days past due   |
| 61-90   | number | 61–90 days past due   |
| 90+     | number | Over 90 days past due |
| total   | number | Sum of all buckets    |

### 3.6 Dashboard Summary (Composite)

**DashboardSummary:**

| Field        | Type                                                     |
| ------------ | -------------------------------------------------------- |
| financials   | FinancialDashboard                                       |
| bankAccounts | BankAccount\[]                                           |
| receivables  | `{ aging: AgingBucket; openInvoices: InvoiceSummary[] }` |
| payables     | `{ aging: AgingBucket; openBills: BillSummary[] }`       |
| generatedAt  | string (ISO timestamp)                                   |

### 3.7 Scheduler Types

**ScheduledJob:**

| Field       | Type                                           |
| ----------- | ---------------------------------------------- |
| id          | string                                         |
| name        | string                                         |
| description | string                                         |
| skillId     | string                                         |
| schedule    | `{ cronExpression: string; enabled: boolean }` |

**JobExecution:**

| Field       | Type                                                |
| ----------- | --------------------------------------------------- |
| jobId       | string                                              |
| executionId | string                                              |
| startTime   | string                                              |
| endTime     | string (optional)                                   |
| status      | `'pending' \| 'running' \| 'completed' \| 'failed'` |
| result      | unknown (optional)                                  |
| error       | string (optional)                                   |

**SchedulerStatus:**

| Field            | Type            |
| ---------------- | --------------- |
| running          | boolean         |
| jobCount         | number          |
| recentExecutions | JobExecution\[] |

**HealthStatus:**

| Field     | Type   |
| --------- | ------ |
| status    | string |
| service   | string |
| version   | string |
| timestamp | string |

***

## 4. API Endpoints (External HH Finance Agent)

All endpoints are on the external HH Finance Agent service (33 total).

### Health

| Method | Path      | Response Type |
| ------ | --------- | ------------- |
| GET    | `/health` | HealthStatus  |

### Dashboard

| Method | Path                     | Response Type                                            |
| ------ | ------------------------ | -------------------------------------------------------- |
| GET    | `/api/dashboard`         | `{ dashboard: FinancialDashboard; metrics: KeyMetrics }` |
| GET    | `/api/dashboard/summary` | DashboardSummary                                         |

### Accounts

| Method | Path                 | Response Type                    |
| ------ | -------------------- | -------------------------------- |
| GET    | `/api/accounts`      | `{ accounts: AccountSummary[] }` |
| GET    | `/api/accounts/bank` | `{ accounts: BankAccount[] }`    |

### Customers / Accounts Receivable

| Method | Path                          | Response Type                         |
| ------ | ----------------------------- | ------------------------------------- |
| GET    | `/api/customers`              | `{ customers: CustomerSummary[] }`    |
| GET    | `/api/customers/with-balance` | `{ customers: CustomerSummary[] }`    |
| GET    | `/api/ar/total`               | `{ totalAccountsReceivable: number }` |
| GET    | `/api/ar/aging`               | `{ aging: AgingBucket }`              |

### Vendors / Accounts Payable

| Method | Path                        | Response Type                      |
| ------ | --------------------------- | ---------------------------------- |
| GET    | `/api/vendors`              | `{ vendors: VendorSummary[] }`     |
| GET    | `/api/vendors/with-balance` | `{ vendors: VendorSummary[] }`     |
| GET    | `/api/ap/total`             | `{ totalAccountsPayable: number }` |
| GET    | `/api/ap/aging`             | `{ aging: AgingBucket }`           |

### Invoices

| Method | Path                      | Response Type                    |
| ------ | ------------------------- | -------------------------------- |
| GET    | `/api/invoices?limit={n}` | `{ invoices: InvoiceSummary[] }` |
| GET    | `/api/invoices/open`      | `{ invoices: InvoiceSummary[] }` |
| GET    | `/api/invoices/overdue`   | `{ invoices: InvoiceSummary[] }` |

### Bills

| Method | Path                       | Response Type              |
| ------ | -------------------------- | -------------------------- |
| GET    | `/api/bills?limit={n}`     | `{ bills: BillSummary[] }` |
| GET    | `/api/bills/open`          | `{ bills: BillSummary[] }` |
| GET    | `/api/bills/overdue`       | `{ bills: BillSummary[] }` |
| GET    | `/api/bills/due-this-week` | `{ bills: BillSummary[] }` |

### Reports

| Method | Path                                         | Response Type                                         |
| ------ | -------------------------------------------- | ----------------------------------------------------- |
| GET    | `/api/reports/pnl?startDate={s}&endDate={e}` | `{ report: unknown; period: { startDate; endDate } }` |
| GET    | `/api/reports/balance-sheet`                 | `{ report: unknown }`                                 |
| GET    | `/api/reports/aging`                         | `{ report: unknown }`                                 |
| GET    | `/api/reports/top-expenses?limit={n}`        | `{ expenses: unknown[] }`                             |
| GET    | `/api/reports/top-income?limit={n}`          | `{ income: unknown[] }`                               |

<Warning>
  Report endpoints return `unknown` types — the response schemas are not fully typed in the frontend. This is a gap that should be addressed when report rendering is implemented.
</Warning>

### Scheduler

| Method | Path                                 | Response Type                            |
| ------ | ------------------------------------ | ---------------------------------------- |
| GET    | `/api/scheduler/status`              | SchedulerStatus                          |
| GET    | `/api/scheduler/jobs`                | `{ jobs: ScheduledJob[] }`               |
| POST   | `/api/scheduler/jobs/{jobId}/run`    | `{ execution: JobExecution }`            |
| PUT    | `/api/scheduler/jobs/{jobId}/toggle` | `{ success: boolean; enabled: boolean }` |
| POST   | `/api/scheduler/start`               | `{ started: boolean }`                   |
| POST   | `/api/scheduler/stop`                | `{ stopped: boolean }`                   |

### Tools

| Method | Path                            | Response Type          |
| ------ | ------------------------------- | ---------------------- |
| GET    | `/api/tools`                    | `{ tools: unknown[] }` |
| POST   | `/api/tools/{toolName}/execute` | `{ result: unknown }`  |

***

## 5. Frontend Components

### Component Hierarchy

```mermaid theme={null}
flowchart TD
    FD["FinanceDashboard"] --> MC["MetricCard (x5)"]
    FD --> BAT["BankAccountsTable"]
    FD --> AS1["AgingSummary (AR)"]
    FD --> AS2["AgingSummary (AP)"]
    FD --> TT1["TransactionsTable (Invoices)"]
    FD --> TT2["TransactionsTable (Bills)"]
    FD --> SS["SchedulerStatus (conditional)"]
```

### Components

| Component         | File                               | Purpose                                                     |
| ----------------- | ---------------------------------- | ----------------------------------------------------------- |
| FinanceDashboard  | `components/FinanceDashboard.tsx`  | Main dashboard: header, metrics grid, tables, scheduler     |
| MetricCard        | `components/MetricCard.tsx`        | Single financial metric with label and formatted value      |
| AgingSummary      | `components/AgingSummary.tsx`      | AR or AP aging buckets display                              |
| BankAccountsTable | `components/BankAccountsTable.tsx` | Bank accounts with names, types, and balances               |
| TransactionsTable | `components/TransactionsTable.tsx` | Invoices or bills table (configurable, limit 5 per section) |
| SchedulerStatus   | `components/SchedulerStatus.tsx`   | Scheduled job list with run/toggle actions                  |

### React Hooks

| Hook                  | Return Type                                             | Source                                                |
| --------------------- | ------------------------------------------------------- | ----------------------------------------------------- |
| useFinanceDashboard() | `{ summary, scheduler, jobs, loading, error, refetch }` | Combines summary + scheduler + jobs via `Promise.all` |
| useHealthStatus()     | HealthStatus                                            | API health check                                      |
| useDashboardSummary() | DashboardSummary                                        | Full dashboard data                                   |
| useBankAccounts()     | BankAccount\[]                                          | Bank accounts list                                    |
| useOpenInvoices()     | InvoiceSummary\[]                                       | Open (unpaid) invoices                                |
| useOverdueInvoices()  | InvoiceSummary\[]                                       | Past-due invoices                                     |
| useOpenBills()        | BillSummary\[]                                          | Open (unpaid) bills                                   |
| useBillsDueThisWeek() | BillSummary\[]                                          | Bills due within 7 days                               |
| useSchedulerStatus()  | SchedulerStatus                                         | Scheduler running state                               |
| useScheduledJobs()    | ScheduledJob\[]                                         | Job definitions list                                  |

Hook pattern: Generic `useAsync<T>` with `data: T | null`, `loading: boolean`, `error: string | null`, `refetch()`.

### Formatting Utilities

| Function                       | Output Example | Description        |
| ------------------------------ | -------------- | ------------------ |
| formatCurrency(amount)         | `$1,234`       | USD, no decimals   |
| formatCurrencyPrecise(amount)  | `$1,234.56`    | USD, 2 decimals    |
| formatDate(dateStr)            | `Jan 15, 2024` | Full date          |
| formatDateShort(dateStr)       | `Jan 15`       | No year            |
| formatNumber(num)              | `1,234`        | Comma-separated    |
| formatPercent(value, decimals) | `12.5%`        | Percentage         |
| formatRatio(value)             | `1.23`         | 2 decimal places   |
| formatRelativeTime(dateStr)    | `2 hours ago`  | Relative timestamp |

### Routes

| Route                                    | Component                                                |
| ---------------------------------------- | -------------------------------------------------------- |
| `/organization/:organization/hh-finance` | FinanceDashboard (wrapped with `withOrganizationHeader`) |

### Dashboard Layout

1. **Header:** Title, subtitle with `generatedAt` timestamp, Refresh button
2. **Metrics Grid (5 cards):** Total Assets, Total Liabilities, Total Equity, Accounts Receivable total, Accounts Payable total
3. **Bank Accounts:** Table of bank accounts with balances
4. **Two-Column Grid:** AR Aging Summary | AP Aging Summary
5. **Two-Column Grid:** Open Invoices (limit 5) | Open Bills (limit 5)
6. **Scheduler** (conditional — shown only when scheduler exists and has jobs): Job list with run/toggle controls

### State Management

* **Loading:** Shows "Loading financial data..." message
* **Error:** Alert banner with error message and Retry button
* **Empty:** "No data available" message
* **Actions:** `handleRunJob(jobId)` triggers manual job execution; `handleToggleJob(jobId, enabled)` enables/disables a job; `refetch()` refreshes all data

***

## 6. Business Rules and Validation

| Rule                    | Description                                                                               | Enforcement                                                 |
| ----------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| External API dependency | Dashboard is non-functional if HH Finance Agent is unreachable                            | Loading/error states in UI                                  |
| No authentication       | API calls carry no credentials — the finance agent must be network-restricted             | Deployment configuration                                    |
| No permission gating    | Route accessible to all authenticated org members, regardless of role                     | `withOrganizationHeader` HOC (no explicit permission check) |
| Report types untyped    | P\&L, balance sheet, aging report, top expenses, top income return `unknown`              | Renders must handle arbitrary structures                    |
| Scheduler controls      | Running/stopping the scheduler and executing jobs are admin-level operations with no auth | Agent-side access control needed                            |
| Data freshness          | `generatedAt` timestamp indicates when the summary was last computed                      | Displayed in dashboard header                               |
| Error handling          | Hooks catch errors as strings; no retry logic or request timeouts                         | Errors displayed in UI; manual retry via Refresh            |

***

## 7. Acceptance Criteria

* [ ] Dashboard loads and displays 5 metric cards (total assets, liabilities, equity, AR, AP)
* [ ] Bank accounts table shows all accounts with names, types, and balances
* [ ] AR and AP aging summaries display correct bucket totals (current, 1-30, 31-60, 61-90, 90+)
* [ ] Open invoices table shows up to 5 invoices with status indicators
* [ ] Open bills table shows up to 5 bills with status indicators
* [ ] Refresh button fetches fresh data from the finance agent
* [ ] Loading state is displayed while data is fetching
* [ ] Error state displays the error message with a Retry option
* [ ] Scheduler section appears only when jobs exist
* [ ] Scheduled jobs can be manually run and toggled on/off
* [ ] All currency values are formatted as USD
* [ ] Relative timestamps display correctly (e.g., "2 hours ago")
* [ ] `REACT_APP_HH_FINANCE_API_URL` environment variable configures the API base URL
* [ ] Dashboard is accessible from the organization navigation

***

## 8. Risks and Edge Cases

| Risk                       | Impact                                                            | Mitigation                                                           |
| -------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------- |
| No API authentication      | Unauthorized access to financial data if agent is network-exposed | Restrict agent to localhost or private network; add API key auth     |
| No route permission gating | Non-admin members see financial data                              | Add `canEditBilling` or a new `viewFinance` permission guard         |
| Finance agent downtime     | Dashboard is completely non-functional                            | Show clear "service unavailable" message; cache last-known-good data |
| Untyped report responses   | Report rendering may fail on unexpected data shapes               | Define report response schemas; validate before rendering            |
| No request timeouts        | Slow agent responses block the UI indefinitely                    | Add timeout to `apiFetch` (e.g., 30 second abort controller)         |
| No retry logic             | Transient failures require manual page refresh                    | Add automatic retry with exponential backoff in hooks                |
| QuickBooks sync lag        | Dashboard may show stale data from QBO                            | Display `generatedAt` timestamp prominently; add manual sync trigger |
| Large data sets            | Hundreds of invoices/bills may slow the agent response            | Pagination via `?limit=` parameter; default to 5 on dashboard        |

***

## 9. Dependencies

| Dependency                         | Type                                 | Status                        |
| ---------------------------------- | ------------------------------------ | ----------------------------- |
| 001-Authentication (user sessions) | Required before                      | DRAFT                         |
| 002-Organization Management        | Required before                      | DRAFT                         |
| External: HH Finance Agent service | Required — separate deployment       | Running locally on port 19792 |
| External: QuickBooks Online        | Required — finance agent data source | Connected via finance agent   |

***

## 10. Related: FFM Sync Engine

<Info>
  The **HH Finance Agent** (Spec 009) and the **FFM Sync Engine** (FFM Spec 007) are related but separate systems that both interact with QuickBooks Online.
</Info>

| System                                                                  | Purpose                                                    | Stack                               | QBO Interaction                                             |
| ----------------------------------------------------------------------- | ---------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------- |
| **HH Finance Agent** (this spec)                                        | Real-time financial dashboard — reads QBO data for display | Standalone REST API on port 19792   | Read-only: fetches accounts, invoices, bills, reports       |
| **FFM Sync Engine** ([Spec 007](https://docs.shawnowen.us/sync-engine)) | Automated data pipeline — writes financial data to QBO     | Next.js 16, Prisma 7, PostgreSQL 15 | Read + Write: imports IIF/CSV data, creates journal entries |

### Integration Architecture

```mermaid theme={null}
flowchart LR
    subgraph "Spec 009 — Finance Dashboard"
        Agent["HH Finance Agent\n(port 19792)"]
        Dashboard["equa-web\nFinance Dashboard"]
    end

    subgraph "FFM Spec 007 — Sync Engine"
        Pipeline["Sync Pipeline\n(7-stage)"]
        Health["GET /api/sync/health"]
    end

    Dashboard -->|"HTTP fetch"| Agent
    Agent -->|"Read"| QBO["QuickBooks\nOnline API"]
    Pipeline -->|"Read + Write"| QBO
```

### Future Integration Points

* The Finance Dashboard could display FFM sync health status via the [GET /api/sync/health](https://docs.shawnowen.us/api/sync-health) endpoint
* Both systems share the same QBO realm for Hills & Hollows, LLC (`qboRealmId`)
* The FFM [circuit breaker](https://docs.shawnowen.us/patterns/circuit-breaker) state could inform the dashboard's data freshness indicator

### FFM Documentation

* [FFM Overview](https://docs.shawnowen.us) — Architecture, tech stack, roadmap
* [Sync Engine Architecture](https://docs.shawnowen.us/sync-engine) — Pipeline, mapping, resilience
* [Data Model](https://docs.shawnowen.us/data-model) — Prisma schema, Spec 007 additions
