Skip to main content

SPEC 009 — Finance Dashboard

FieldValue
StatusDRAFT
PriorityP1 — Core Product
BackendExternal: HH Finance Agent API (separate service, not equa-server)
APIequa-web/src/modules/hh-finance/services/api.ts (client-side API layer)
Frontendequa-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:
  • 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

ComponentPath
Module rootequa-web/src/modules/hh-finance/
Componentsequa-web/src/modules/hh-finance/components/
React hooksequa-web/src/modules/hh-finance/hooks/useFinanceData.ts
API serviceequa-web/src/modules/hh-finance/services/api.ts
Type definitionsequa-web/src/modules/hh-finance/types/index.ts
Formattersequa-web/src/modules/hh-finance/utils/formatters.ts

2.3 External API Configuration

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:
FieldTypeDescription
totalAssetsnumberSum of all asset accounts
totalLiabilitiesnumberSum of all liability accounts
totalEquitynumberAssets minus liabilities
netIncomenumberoptional, P&L net income
cashOnHandnumberoptional, liquid cash balance
KeyMetrics:
FieldTypeDescription
currentRationumberCurrent assets / current liabilities
quickRationumber(Current assets - inventory) / current liabilities
workingCapitalnumberCurrent assets - current liabilities
daysReceivablenumberAverage days to collect receivables
daysPayablenumberAverage days to pay payables

3.2 Account Types

BankAccount:
FieldType
accountIdstring
accountNamestring
accountTypestring
balancenumber
AccountSummary:
FieldType
idstring
namestring
typestring
subTypestring (optional)
balancenumber
activeboolean

3.3 Customer and Vendor Types

CustomerSummary / VendorSummary (same structure):
FieldType
idstring
displayNamestring
companyNamestring (optional)
emailstring (optional)
phonestring (optional)
balancenumber
activeboolean

3.4 Transaction Types

InvoiceSummary:
FieldTypeDescription
idstringInvoice ID
docNumberstring (optional)Invoice number
datestringIssue date
dueDatestring (optional)Payment due date
customerNamestringCustomer display name
customerIdstringCustomer ID
totalnumberInvoice total amount
balancenumberOutstanding balance
statusInvoiceStatus'paid' | 'partial' | 'open' | 'overdue'
BillSummary:
FieldTypeDescription
idstringBill ID
docNumberstring (optional)Bill number
datestringIssue date
dueDatestring (optional)Payment due date
vendorNamestringVendor display name
vendorIdstringVendor ID
totalnumberBill total amount
balancenumberOutstanding balance
statusBillStatus'paid' | 'partial' | 'open' | 'overdue'

3.5 Aging

AgingBucket:
FieldTypeDescription
currentnumberNot yet due
1-30number1–30 days past due
31-60number31–60 days past due
61-90number61–90 days past due
90+numberOver 90 days past due
totalnumberSum of all buckets

3.6 Dashboard Summary (Composite)

DashboardSummary:
FieldType
financialsFinancialDashboard
bankAccountsBankAccount[]
receivables{ aging: AgingBucket; openInvoices: InvoiceSummary[] }
payables{ aging: AgingBucket; openBills: BillSummary[] }
generatedAtstring (ISO timestamp)

3.7 Scheduler Types

ScheduledJob:
FieldType
idstring
namestring
descriptionstring
skillIdstring
schedule{ cronExpression: string; enabled: boolean }
JobExecution:
FieldType
jobIdstring
executionIdstring
startTimestring
endTimestring (optional)
status'pending' | 'running' | 'completed' | 'failed'
resultunknown (optional)
errorstring (optional)
SchedulerStatus:
FieldType
runningboolean
jobCountnumber
recentExecutionsJobExecution[]
HealthStatus:
FieldType
statusstring
servicestring
versionstring
timestampstring

4. API Endpoints (External HH Finance Agent)

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

Health

MethodPathResponse Type
GET/healthHealthStatus

Dashboard

MethodPathResponse Type
GET/api/dashboard{ dashboard: FinancialDashboard; metrics: KeyMetrics }
GET/api/dashboard/summaryDashboardSummary

Accounts

MethodPathResponse Type
GET/api/accounts{ accounts: AccountSummary[] }
GET/api/accounts/bank{ accounts: BankAccount[] }

Customers / Accounts Receivable

MethodPathResponse 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

MethodPathResponse 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

MethodPathResponse Type
GET/api/invoices?limit={n}{ invoices: InvoiceSummary[] }
GET/api/invoices/open{ invoices: InvoiceSummary[] }
GET/api/invoices/overdue{ invoices: InvoiceSummary[] }

Bills

MethodPathResponse 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

MethodPathResponse 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[] }
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.

Scheduler

MethodPathResponse Type
GET/api/scheduler/statusSchedulerStatus
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

MethodPathResponse Type
GET/api/tools{ tools: unknown[] }
POST/api/tools/{toolName}/execute{ result: unknown }

5. Frontend Components

Component Hierarchy

Components

ComponentFilePurpose
FinanceDashboardcomponents/FinanceDashboard.tsxMain dashboard: header, metrics grid, tables, scheduler
MetricCardcomponents/MetricCard.tsxSingle financial metric with label and formatted value
AgingSummarycomponents/AgingSummary.tsxAR or AP aging buckets display
BankAccountsTablecomponents/BankAccountsTable.tsxBank accounts with names, types, and balances
TransactionsTablecomponents/TransactionsTable.tsxInvoices or bills table (configurable, limit 5 per section)
SchedulerStatuscomponents/SchedulerStatus.tsxScheduled job list with run/toggle actions

React Hooks

HookReturn TypeSource
useFinanceDashboard(){ summary, scheduler, jobs, loading, error, refetch }Combines summary + scheduler + jobs via Promise.all
useHealthStatus()HealthStatusAPI health check
useDashboardSummary()DashboardSummaryFull 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()SchedulerStatusScheduler running state
useScheduledJobs()ScheduledJob[]Job definitions list
Hook pattern: Generic useAsync<T> with data: T | null, loading: boolean, error: string | null, refetch().

Formatting Utilities

FunctionOutput ExampleDescription
formatCurrency(amount)$1,234USD, no decimals
formatCurrencyPrecise(amount)$1,234.56USD, 2 decimals
formatDate(dateStr)Jan 15, 2024Full date
formatDateShort(dateStr)Jan 15No year
formatNumber(num)1,234Comma-separated
formatPercent(value, decimals)12.5%Percentage
formatRatio(value)1.232 decimal places
formatRelativeTime(dateStr)2 hours agoRelative timestamp

Routes

RouteComponent
/organization/:organization/hh-financeFinanceDashboard (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

RuleDescriptionEnforcement
External API dependencyDashboard is non-functional if HH Finance Agent is unreachableLoading/error states in UI
No authenticationAPI calls carry no credentials — the finance agent must be network-restrictedDeployment configuration
No permission gatingRoute accessible to all authenticated org members, regardless of rolewithOrganizationHeader HOC (no explicit permission check)
Report types untypedP&L, balance sheet, aging report, top expenses, top income return unknownRenders must handle arbitrary structures
Scheduler controlsRunning/stopping the scheduler and executing jobs are admin-level operations with no authAgent-side access control needed
Data freshnessgeneratedAt timestamp indicates when the summary was last computedDisplayed in dashboard header
Error handlingHooks catch errors as strings; no retry logic or request timeoutsErrors 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

RiskImpactMitigation
No API authenticationUnauthorized access to financial data if agent is network-exposedRestrict agent to localhost or private network; add API key auth
No route permission gatingNon-admin members see financial dataAdd canEditBilling or a new viewFinance permission guard
Finance agent downtimeDashboard is completely non-functionalShow clear “service unavailable” message; cache last-known-good data
Untyped report responsesReport rendering may fail on unexpected data shapesDefine report response schemas; validate before rendering
No request timeoutsSlow agent responses block the UI indefinitelyAdd timeout to apiFetch (e.g., 30 second abort controller)
No retry logicTransient failures require manual page refreshAdd automatic retry with exponential backoff in hooks
QuickBooks sync lagDashboard may show stale data from QBODisplay generatedAt timestamp prominently; add manual sync trigger
Large data setsHundreds of invoices/bills may slow the agent responsePagination via ?limit= parameter; default to 5 on dashboard

9. Dependencies

DependencyTypeStatus
001-Authentication (user sessions)Required beforeDRAFT
002-Organization ManagementRequired beforeDRAFT
External: HH Finance Agent serviceRequired — separate deploymentRunning locally on port 19792
External: QuickBooks OnlineRequired — finance agent data sourceConnected via finance agent

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.
SystemPurposeStackQBO Interaction
HH Finance Agent (this spec)Real-time financial dashboard — reads QBO data for displayStandalone REST API on port 19792Read-only: fetches accounts, invoices, bills, reports
FFM Sync Engine (Spec 007)Automated data pipeline — writes financial data to QBONext.js 16, Prisma 7, PostgreSQL 15Read + Write: imports IIF/CSV data, creates journal entries

Integration Architecture

Future Integration Points

  • The Finance Dashboard could display FFM sync health status via the GET /api/sync/health endpoint
  • Both systems share the same QBO realm for Hills & Hollows, LLC (qboRealmId)
  • The FFM circuit breaker state could inform the dashboard’s data freshness indicator

FFM Documentation