Add dark mode support using Mantine's built-in color scheme system,
persisted via a new Zustand preferences store. Includes a quick toggle
in the app header and an enabled switch in User Preferences. Also
removes the "AI Health Scores" title from the dashboard to reclaim
vertical space.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Usage: ./scripts/reset-password.sh <email> <new-password>
Generates bcrypt hash via bcryptjs in the backend container,
updates the database, and verifies the hash matches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace misleading 'sent' status with 'pending' (no email capability)
- Show assessment group name instead of raw 'regular_assessment' type
- Add owner last name to invoice table
- Fix payment creation Internal Server Error (PostgreSQL $2 type cast)
- Add edit/delete capability for payment records with invoice recalc
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Assessment groups can now define billing frequency (monthly, quarterly,
annual) with configurable due months and due day. Invoice generation
respects each group's schedule - only generating invoices when the
selected month is a billing month for that group. Adds a generation
preview showing which groups will be billed, period tracking on
invoices, and billing period context in the payments UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Make all AI endpoints (health scores + investment recommendations)
fire-and-forget: POST returns immediately, frontend polls for results
- Extend AI API timeout from 2-5 min to 10 min for both services
- Add "last analysis failed — showing cached data" message to the
Investment Recommendations panel (matches health score widgets)
- Add status/error_message columns to ai_recommendations table
- Remove nginx AI timeout overrides (no longer needed)
- Users can now navigate away during AI processing without interruption
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address 4 issues identified in AI feature audit:
1. Reduce temperature from 0.3 to 0.1 for health score calculations
to reduce 16-40 point score volatility across runs
2. Add explicit cash runway classification rules to operating prompt
preventing the model from rating sub-3-month runway as "positive"
3. Pre-compute total special assessment income in both operating and
reserve prompts, eliminating per-unit vs total confusion ($300
vs $20,100)
4. Make YTD budget comparison actuals-aware: only compare months with
posted journal entries, show current month budget separately, and
add prompt guidance about month-end posting cadence
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix systemic LEFT JOIN date filter bug in Balance Sheet, Income Statement,
and Cash Flow Statement by using parenthesized INNER JOIN pattern so
SUM(jel.debit/credit) respects date parameters
- Add Current Year Net Income synthetic equity line to Balance Sheet to
satisfy the accounting equation (A = L + E) during open fiscal periods
- Add investment_accounts balances to Balance Sheet assets and corresponding
equity lines for reserve/operating investment holdings
- Fix Cash Flow Statement beginning/ending cash always showing $0 by
replacing LIKE '%Cash%' filter with account_type = 'asset'
- Fix Year-End Package HTTP 500 by replacing broken invoices.vendor_id
query with journal-entry-based vendor payment lookup
- Fix Quarterly Report defaulting to previous quarter instead of current
- Fix Quarterly Report date subtitle off-by-one day from UTC parsing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers vertical tuning, managed service offloading, horizontal scaling
with replicas, and multi-node strategies. Includes resource budgets for
the current 4-core/24GB VM, monitoring thresholds for New Relic alerts,
PostgreSQL/Redis tuning values, and a scaling decision tree.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend reaches postgres and redis over the Docker network (hoanet),
so host port mappings are unnecessary. Removing them eliminates 4
docker-proxy processes and closes 0.0.0.0:5432 and 0.0.0.0:6379
which were publicly reachable — a security and performance fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Health Scores — separate operating/reserve refresh
- Added POST /health-scores/calculate/operating and /calculate/reserve
- Each health card now has its own Refresh button
- On failure, shows cached (last good) data with "last analysis failed"
watermark instead of blank "Error calculating score"
- Backend getLatestScores returns latest complete score + failure flag
2. Investment Planning — increased AI timeout to 5 minutes
- Backend callAI timeout: 180s → 300s
- Frontend axios timeout: set explicitly to 300s (was browser default)
- Host nginx proxy_read_timeout: 180s → 300s
- Loading message updated to reflect longer wait times
3. Capital Planning — Unscheduled column moved to rightmost position
- Kanban column order: current year → future → unscheduled (was leftmost)
- Puts immediate/near-term projects front and center
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Puppeteer's bundled Chrome often fails on Linux servers due to
architecture mismatches. Now auto-detects system-installed Chromium
at common paths (/usr/bin/chromium-browser, /usr/bin/chromium), or
honors PUPPETEER_EXECUTABLE_PATH env var.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous scraper was picking up Bankrate's summary table
(.wealth-product-rate-list) which only has "best rates" per term with
no bank names, resulting in entries like "Top CD Rate - 1 year".
Now targets the actual bank offer cards in .wrt-RateSections-sponsoredoffers
and .wrt-RateSections-additionaloffers sections. Key changes:
- Extract bank names from img[alt] (logo) with text-based fallbacks
- Fix APY parsing to avoid Bankrate score leaking in (e.g. "4.5" score
concatenated with "4.00%" APY was parsed as 0.4%)
- Handle both "Min. deposit" (CDs) and "Min. balance for APY" (savings/MM)
- Parse abbreviated terms from Bankrate (e.g. "1yr", "14mo")
- Strip product suffixes from bank names (e.g. "Synchrony Bank CD" → "Synchrony Bank")
- Filter out entries that aren't real banks (terms, dollar amounts)
- Keep a fallback strategy for future Bankrate layout changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Empty string date values from the frontend were being passed directly
to PostgreSQL, which cannot cast "" to DATE. Normalize empty strings
to null for all date columns in the update method and the dedicated
updatePlannedDate endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Node.js New Relic agent with an on/off switch via NEW_RELIC_ENABLED
in .env. Uses a preload script (-r newrelic-preload.js) so the agent
loads before all other modules. Configured entirely through environment
variables — no newrelic.js config file needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Docker Compose merges port arrays from base + override files, so the
base 3000:3000 and the prod overlay 127.0.0.1:3000:3000 conflicted.
Removed direct host port mappings from the base — dev traffic already
routes through the Docker nginx on port 80 via the bridge network.
The prod overlay cleanly adds loopback-only mappings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The production stack no longer runs a Docker nginx container. Instead,
the host-level nginx handles SSL termination AND request routing:
/api/* → 127.0.0.1:3000 (backend)
/* → 127.0.0.1:3001 (frontend)
Changes:
- docker-compose.prod.yml: set nginx replicas to 0, expose backend and
frontend on 127.0.0.1 only (loopback)
- nginx/host-production.conf: new ready-to-copy host nginx config with
SSL, rate limiting, proxy buffering, and AI endpoint timeouts
- docs/DEPLOYMENT.md: rewritten production deployment and SSL sections
to reflect the simplified single-nginx architecture
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The base docker-compose.yml maps nginx to 80:80, which conflicts with
the host-level nginx that handles SSL termination on production servers.
The production overlay now explicitly maps to 8080:80 so the host proxy
can forward to localhost:8080. Updated DEPLOYMENT.md with host reverse
proxy setup instructions and corrected architecture diagrams.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Frontend container nginx listens on 3001 instead of 80 to avoid
conflicts with the host-level reverse proxy
- Removed certbot service, volumes, and SSL config from
docker-compose.prod.yml — SSL/certbot is managed at the host level
- Updated nginx/production.conf: HTTP-only (host handles TLS),
upstream frontend points to port 3001
- Updated nginx/ssl.conf frontend upstream to 3001 for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause of 502 errors under 30 concurrent users: the production server
was running dev-mode infrastructure (Vite dev server, NestJS --watch,
no DB connection pooling, single Node.js process).
Changes:
- backend/Dockerfile: multi-stage prod build (compiled JS, no devDeps)
- frontend/Dockerfile: multi-stage prod build (static assets served by nginx)
- frontend/nginx.conf: SPA routing config for frontend container
- docker-compose.prod.yml: production overlay with tuned Postgres, memory
limits, health checks, restart policies
- nginx/production.conf: keepalive upstreams, proxy buffering, rate limiting
- backend/src/main.ts: Node.js clustering (1 worker per CPU, up to 4),
conditional request logging, production CORS
- backend/src/app.module.ts: TypeORM connection pool (max 30, min 5)
- docs/DEPLOYMENT.md: new Production Deployment section
Deploy with: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The package was imported by AppTour.tsx but missing from package.json,
causing a build failure on fresh installs / production deploys.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
scripts/db-backup.sh provides three commands:
- backup: creates a timestamped, gzipped pg_dump (custom format) in ./backups/
- restore: drops, recreates, and loads a backup with confirmation prompt
- list: shows available backups with sizes and dates
Supports --keep N flag for automatic pruning of backups older than N days,
making it cron-friendly for daily automated backups. Also adds backups/
and *.dump.gz to .gitignore.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers fresh server setup, environment configuration, database backup
(full and per-tenant), restore into staged environment, migration
execution, and verification steps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Projects imported via CSV that lack a target_year were invisible in Capital
Planning because findForPlanning() filtered on target_year IS NOT NULL. This
removes that filter and adds an "Unscheduled" Kanban column (orange background,
2-col layout) so users can drag unscheduled projects into year buckets.
Also bumps app version to 2026.3.2 (beta).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Projects with target years beyond the 5-year planning window now
appear in the Future column of the Kanban board (previously they
were invisible). Cards for these projects show their specific target
year as a badge. The Future column uses a 2-column grid layout when
it has more than 3 projects to maximize screen utilization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reserve fund analysis now includes 12-month forward projection with
special assessment income (by frequency), monthly budget data,
capital project costs, and investment maturities. AI prompt updated
to evaluate projected reserve liquidity and timing risks.
Both health score dashboard cards now show a subtle "Last updated"
timestamp at the bottom.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The operating health score now includes forward-looking cash flow
projections using monthly budget data, assessment income schedules,
and operating project costs. AI prompt updated to evaluate projected
liquidity, timing risks, and year-end cash position.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add daily AI health score calculation (0-100) for both operating and
reserve funds. Scores include trajectory tracking, factor analysis,
recommendations, and data readiness checks. Dashboard displays
graphical RingProgress gauges with color-coded scores, trend
indicators, and expandable detail popovers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The global WriteAccessGuard was checking req.user.role, but req.user is
set by JwtAuthGuard (a per-controller guard) which runs AFTER global guards.
TenantMiddleware sets req.userRole from the JWT before guards execute,
so we now check that property first.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add global WriteAccessGuard that blocks POST/PUT/PATCH/DELETE for viewer role
- Add @AllowViewer() decorator for endpoints viewers need (switch-org, intro-seen, AI recommendations)
- Add useIsReadOnly hook to auth store for frontend role checks
- Hide write UI (add/edit/delete/import buttons, inline editors) in all 13 data pages for viewers
- Disable inline NumberInputs on Budgets and Monthly Actuals pages for viewers
- Skip onboarding wizard for viewer role users
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Dashboard: Remove tenant name/role subtitle
- Cash Flow: Replace Operating/Reserve net cards with inflow vs outflow
breakdown showing In/Out amounts and signed net; replace Ending Cash
card with AI Financial Health status from saved recommendation
- Accounts: Auto-set first asset account per fund_type as primary on creation
- Investments: Add 5th summary card for projected annual interest earnings
- Sankey: Add Actuals/Budget/Forecast data source toggle and
All Funds/Operating/Reserve fund filter SegmentedControls with
backend support for budget-based and forecast (actuals+budget) queries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Feature 1 - How-To Intro Tour (react-joyride):
- 8-step guided walkthrough highlighting Dashboard, Accounts, Assessments,
Transactions, Budgets, Reports, and AI Investment Planning
- Runs automatically on first login, tracked via has_seen_intro flag on user
- Centralized step config in config/tourSteps.ts for easy text editing
- data-tour attributes on Sidebar nav items and Dashboard for targeting
Feature 2 - Tenant Onboarding Wizard:
- 3-step modal wizard: create operating account, assessment group + units,
import budget CSV
- Runs after tour completes, tracked via onboardingComplete in org settings JSONB
- Reuses existing API endpoints (POST /accounts, /assessment-groups, /units,
/budgets/:year/import)
Backend changes:
- Add has_seen_intro column to shared.users + migration
- Add PATCH /auth/intro-seen endpoint to mark tour complete
- Add PATCH /organizations/settings endpoint for org settings updates
- Include hasSeenIntro in login response, settings in switch-org response
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrap account interest query in subquery to avoid SUM(SUM(...)) nesting
- Replace nonexistent interest_earned column with current_value - principal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix Accounts page: include investment accounts in Est. Monthly Interest calc,
add Fund column to investment table, split summary cards into Operating/Reserve
- Fix Cash Flow: ending balance now respects includeInvestments toggle
- Fix Budget Manager: separate operating/reserve income in summary cards
- Fix Projects: default sort by planned_date instead of name
- Add Vendors: last_negotiated date field with migration, CSV import/export
- New Quarterly Financial Report: budget vs actuals, over-budget flagging, YTD
- Enhance Dashboard: separate Operating/Reserve fund cards, expanded Quick Stats
with monthly interest, YTD interest earned, planned capital spend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rate fetcher now scrapes CD, Money Market, and High Yield Savings rates
from Bankrate.com with pauses between fetches to avoid rate limiting
- Historical rate data is preserved (no longer deleted on each fetch)
- Database migration adds rate_type column and tenant ai_recommendations table
- Backend returns market rates grouped by type with latest-batch-only queries
- AI prompt now includes all three rate types for comprehensive analysis
- AI recommendations are saved per-tenant for retrieval on page load
- Frontend: "Market CD Rates" replaced with "Today's Market Rates" tabbed view
- Rates section is collapsible (expanded by default) to save screen space
- Saved recommendations load automatically with "Last Updated" timestamp
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enhancement 1 - Block suspended/archived org access:
- Add org status check in switchOrganization() (auth.service.ts)
- Filter suspended/archived orgs from login response (generateTokenResponse)
- Add org status guard with 60s cache in TenantMiddleware
- Frontend: filter orgs in SelectOrgPage, add 403 handler in api.ts
Enhancement 2 - Change tenant plan level:
- Add updatePlanLevel() to organizations.service.ts
- Add PUT /admin/organizations/:id/plan endpoint
- Frontend: clickable plan dropdown in Organizations table + confirmation modal
- Plan level Select in tenant detail drawer
Enhancement 3 - User impersonation:
- Add impersonateUser() to auth.service.ts with impersonatedBy JWT claim
- Add POST /admin/impersonate/:userId endpoint
- Frontend: Impersonate button in Users tab (disabled for admins)
- Impersonation state management in authStore (start/stop/persist)
- Orange impersonation banner in AppLayout header with stop button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TypeORM needs explicit type: 'varchar' for nullable string columns
to avoid reflecting the union type as Object.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Database: Add login_history, ai_recommendation_log tables; is_platform_owner
column on users; subscription fields on organizations (payment_date,
confirmation_number, renewal_date)
- Backend: New AdminAnalyticsService with platform metrics, tenant detail, and
health score calculations (0-100 based on activity, budget, transactions,
members, AI usage)
- Backend: Login/org-switch now records to login_history; AI recommendations
logged to ai_recommendation_log; platform owner protected from superadmin toggle
- Frontend: 4-tab admin panel (Dashboard, Organizations, Users, Tenant Health)
with tenant detail drawer, subscription management, health scoring visualization
- Platform owner account (admin@hoaledgeriq.com) auto-redirects to admin panel
- Seed data includes platform owner account and sample login history
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The AI was making incorrect liquidity warnings because it only saw
current cash positions and annual budget totals — it had no visibility
into the month-by-month cash flow forecast that includes:
- Special assessment collections (reserve fund income from homeowners)
- Monthly budget income/expense breakdown (not just annual totals)
- Investment maturity schedule (when CDs return cash)
- Capital project expense timing
Now the AI receives:
1. Full assessment schedule (regular + special, with frequency)
2. 12-month forward forecast with projected operating/reserve cash,
investment balances, and per-month income/expense drivers
3. Updated system prompt instructing the AI to use the forecast for
liquidity analysis rather than just current balances
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The AI recommendation endpoint calls a large language model (397B params)
which can take 60-120 seconds to respond. Nginx's default 60s proxy_read_timeout
was killing the connection before the response arrived. Added a dedicated
location block with 180s timeout for the recommendations endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add toggle-able debug logging (AI_DEBUG env var) that logs prompts,
request metadata, raw responses, parsed output, and full error chains
- Replace Node.js native fetch() with https module for Docker Alpine
compatibility (fixes "fetch failed" error with large payloads)
- Reduce max_tokens from 16384 to 4096 (qwen3.5 doesn't need thinking
token budget)
- Strip <think> blocks from model responses
- Add AI_DEBUG to docker-compose.yml and .env.example
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>