From b658f50c9c7c0f1befe64faa56e8e8fd0bfbb28d Mon Sep 17 00:00:00 2001 From: olsch01 Date: Wed, 11 Mar 2026 09:54:53 -0400 Subject: [PATCH] Add Chatwoot support widget and AI API setup documentation - Insert Chatwoot chat widget script at bottom of index.html body (connects to chat.hoaledgeriq.com with token 1QMW1fycL5xHvd6XMfg4Dbb4) - Add AI_SETUP.md documenting how to upgrade the benefit calculator from client-side math to live AI API calls (Claude or OpenAI), including endpoint code, app.js changes, prompt tuning, cost estimates, and rate limiting guidance Co-Authored-By: Claude Sonnet 4.6 --- AI_SETUP.md | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 17 ++++ 2 files changed, 236 insertions(+) create mode 100644 AI_SETUP.md diff --git a/AI_SETUP.md b/AI_SETUP.md new file mode 100644 index 0000000..a079dba --- /dev/null +++ b/AI_SETUP.md @@ -0,0 +1,219 @@ +# HOA LedgerIQ — AI API Configuration Guide + +## Overview + +The **Benefit Calculator widget** currently runs entirely client-side using conservative +fixed-rate math. This guide explains how to upgrade it to call a real AI API +(Claude or OpenAI) so the recommendation text is generated dynamically by a language model. + +--- + +## Architecture + +``` +Browser (app.js) + └─► POST /api/calculate (server.js) + └─► Anthropic / OpenAI API + └─► Returns AI-generated investment recommendation text + └─► JSON response back to browser +``` + +--- + +## Step 1 — Add your API key to `.env` + +Open `.env` in the project root and add one of the following: + +```env +# For Claude (Anthropic) — recommended +ANTHROPIC_API_KEY=sk-ant-... + +# OR for OpenAI +OPENAI_API_KEY=sk-... +``` + +--- + +## Step 2 — Install the SDK + +```bash +# Claude (Anthropic) +npm install @anthropic-ai/sdk + +# OR OpenAI +npm install openai +``` + +--- + +## Step 3 — Add the `/api/calculate` endpoint to `server.js` + +Add this block after the existing `/api/health` route: + +### Using Claude (Anthropic) + +```js +const Anthropic = require('@anthropic-ai/sdk'); +const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); + +app.post('/api/calculate', async (req, res) => { + const { homesites, propertyType, annualIncome, paymentFreq, reserveFunds, interest2025 } = req.body ?? {}; + + if (!homesites || !annualIncome) { + return res.status(400).json({ error: 'homesites and annualIncome are required.' }); + } + + const fmt = n => '$' + Math.round(n).toLocaleString(); + const freqLabel = { monthly: 'monthly', quarterly: 'quarterly', annually: 'annual' }[paymentFreq] || 'monthly'; + const typeLabel = { sfh: 'single-family home', townhomes: 'townhome', condos: 'condo', mixed: 'mixed-use' }[propertyType] || ''; + + const prompt = `You are a conservative HOA financial advisor. Given the following community data, provide a brief (3-4 sentence) plain-English investment income recommendation. Use only conservative, realistic estimates. Do not speculate beyond what the data supports. + +Community: ${homesites}-unit ${typeLabel} association +Annual dues income: ${fmt(annualIncome)} (collected ${freqLabel}) +Reserve fund balance: ${fmt(reserveFunds || 0)} +Interest income earned in 2025: ${fmt(interest2025 || 0)} + +Provide a recommendation focused on: +1. How much of the reserve funds could conservatively be invested and in what vehicle (e.g. CD ladder, money market, T-bills) +2. How much operating cash could earn interest between collection and expense periods +3. A realistic estimated annual interest income potential +4. A single sentence comparing that to their 2025 actual if provided + +Keep the tone professional and factual. No bullet points — flowing paragraph only.`; + + try { + const message = await anthropic.messages.create({ + model: 'claude-opus-4-6', + max_tokens: 300, + messages: [{ role: 'user', content: prompt }], + }); + + const text = message.content[0]?.text ?? ''; + res.json({ recommendation: text }); + } catch (err) { + console.error('AI API error:', err.message); + res.status(502).json({ error: 'AI service unavailable. Showing estimated result.' }); + } +}); +``` + +### Using OpenAI (GPT-4o) + +```js +const OpenAI = require('openai'); +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); + +app.post('/api/calculate', async (req, res) => { + const { homesites, propertyType, annualIncome, paymentFreq, reserveFunds, interest2025 } = req.body ?? {}; + + // ... (same prompt construction as above) ... + + try { + const completion = await openai.chat.completions.create({ + model: 'gpt-4o', + max_tokens: 300, + messages: [{ role: 'user', content: prompt }], + }); + + const text = completion.choices[0]?.message?.content ?? ''; + res.json({ recommendation: text }); + } catch (err) { + console.error('AI API error:', err.message); + res.status(502).json({ error: 'AI service unavailable.' }); + } +}); +``` + +--- + +## Step 4 — Update `app.js` to call the API endpoint + +In the `initCalculator` function, replace this line in the submitBtn handler: + +```js +document.getElementById('calcAiText').textContent = ai; // current: client-side text +``` + +With this: + +```js +// Call the AI endpoint; fall back to client-side text if unavailable +try { + const aiRes = await fetch('/api/calculate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ homesites, propertyType, annualIncome, paymentFreq, reserveFunds, interest2025 }), + }); + if (aiRes.ok) { + const { recommendation } = await aiRes.json(); + document.getElementById('calcAiText').textContent = recommendation; + } else { + document.getElementById('calcAiText').textContent = ai; // fallback + } +} catch (_) { + document.getElementById('calcAiText').textContent = ai; // fallback +} +``` + +> Note: The `submitBtn` handler must be declared `async` for the `await` above to work: +> ```js +> submitBtn?.addEventListener('click', async () => { ... }); +> ``` + +--- + +## Step 5 — Restart the server + +```bash +sudo systemctl restart hoaledgeriqweb + +# Verify the endpoint is live +curl -X POST http://localhost:3000/api/calculate \ + -H "Content-Type: application/json" \ + -d '{"homesites":150,"propertyType":"sfh","annualIncome":300000,"paymentFreq":"monthly","reserveFunds":500000,"interest2025":4200}' +``` + +--- + +## Prompt Tuning Tips + +The prompt in Step 3 is the core of the AI's behavior. You can adjust it to: + +| Goal | Change | +|---|---| +| More optimistic estimates | Change "conservative" to "moderate" in the prompt | +| Shorter output | Reduce `max_tokens` to `150` | +| Include specific investment products | Add "mention specific products like Vanguard Federal Money Market or 6-month T-bills" | +| Add a disclaimer | Append "End with one sentence reminding them this is not financial advice." | + +--- + +## Cost Estimate + +| Model | Approx. cost per calculator use | +|---|---| +| Claude Opus 4.6 | ~$0.002 | +| Claude Sonnet 4.6 | ~$0.0004 | +| GPT-4o | ~$0.002 | +| GPT-4o-mini | ~$0.00005 | + +For a landing page with low traffic, even Claude Opus is negligible cost. For scale, +`claude-sonnet-4-6` is the best balance of quality and price. + +--- + +## Security Notes + +- **Never expose your API key in `app.js` or any client-side code.** All AI calls must go through `server.js`. +- Rate-limit the `/api/calculate` endpoint to prevent abuse (e.g. with `express-rate-limit`): + +```bash +npm install express-rate-limit +``` + +```js +const rateLimit = require('express-rate-limit'); +const calcLimiter = rateLimit({ windowMs: 60 * 1000, max: 10 }); // 10 req/min per IP +app.use('/api/calculate', calcLimiter); +``` diff --git a/index.html b/index.html index c23efcb..133543f 100644 --- a/index.html +++ b/index.html @@ -530,5 +530,22 @@ + + +