Add OpenAI-compatible AI investment advisor to benefit calculator
- server.js: add /api/calculate endpoint using openai SDK with configurable AI_API_URL, AI_API_KEY, AI_MODEL, AI_DEBUG env vars (works with any OpenAI-compatible provider: NVIDIA NIM, Together AI, Groq, Ollama, etc.) - app.js: make calculator submit handler async; call /api/calculate with graceful fallback to client-side generated text if AI is unavailable - package.json: add openai and dotenv dependencies - AI_SETUP.md: rewrite to document new unified env var config with provider examples Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
65
server.js
65
server.js
@@ -14,9 +14,20 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const Database = require('better-sqlite3');
|
||||
const OpenAI = require('openai');
|
||||
|
||||
// ── Config ──────────────────────────────────────────────
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// ── AI client (OpenAI-compatible) ────────────────────────
|
||||
const AI_API_URL = process.env.AI_API_URL || 'https://api.openai.com/v1';
|
||||
const AI_API_KEY = process.env.AI_API_KEY || '';
|
||||
const AI_MODEL = process.env.AI_MODEL || 'gpt-4o-mini';
|
||||
const AI_DEBUG = process.env.AI_DEBUG === 'true';
|
||||
|
||||
const aiClient = AI_API_KEY
|
||||
? new OpenAI({ apiKey: AI_API_KEY, baseURL: AI_API_URL })
|
||||
: null;
|
||||
const DB_DIR = path.join(__dirname, 'data');
|
||||
const DB_PATH = path.join(DB_DIR, 'leads.db');
|
||||
|
||||
@@ -127,6 +138,60 @@ app.get('/api/leads', (req, res) => {
|
||||
res.json({ count: leads.length, leads });
|
||||
});
|
||||
|
||||
// POST /api/calculate — AI-powered investment recommendation
|
||||
app.post('/api/calculate', async (req, res) => {
|
||||
if (!aiClient) {
|
||||
return res.status(503).json({ error: 'AI service not configured.' });
|
||||
}
|
||||
|
||||
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.`;
|
||||
|
||||
if (AI_DEBUG) {
|
||||
console.log('[AI_DEBUG] model:', AI_MODEL);
|
||||
console.log('[AI_DEBUG] prompt:', prompt);
|
||||
}
|
||||
|
||||
try {
|
||||
const completion = await aiClient.chat.completions.create({
|
||||
model: AI_MODEL,
|
||||
max_tokens: 300,
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
});
|
||||
|
||||
const text = completion.choices[0]?.message?.content ?? '';
|
||||
|
||||
if (AI_DEBUG) console.log('[AI_DEBUG] response:', 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.' });
|
||||
}
|
||||
});
|
||||
|
||||
// Health check
|
||||
app.get('/api/health', (_req, res) => res.json({ status: 'ok', ts: new Date().toISOString() }));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user