refactor: restructure to clean architecture

fix: restore ScoringConfig improvements lost in refactor commit

docs: rewrite README and CLAUDE.md to reflect current architecture

code-format

code fixes
This commit is contained in:
Kazuma
2026-06-03 00:02:55 -04:00
parent 19fc052d14
commit cd74497de6
60 changed files with 4610 additions and 796 deletions
+82
View File
@@ -0,0 +1,82 @@
// finance.js — Personal Finance Entry Point
//
// Runs independently of the news screener.
// Fetches your accounts from SimpleFIN, screens your portfolio holdings,
// and generates a finance-report.html with:
//
// 1. Net worth + account overview (SimpleFIN)
// 2. Portfolio positions with hold/sell/add advice (screener + crypto prices)
// 3. Spending breakdown (SimpleFIN)
//
// Usage:
// npm run finance
import 'dotenv/config';
import { readFileSync, existsSync } from 'fs';
import { SimpleFINClient } from './src/finance/SimpleFINClient.js';
import { PersonalFinanceAnalyzer } from './src/finance/PersonalFinanceAnalyzer.js';
import { PortfolioAdvisor } from './src/finance/PortfolioAdvisor.js';
import { ScreenerEngine } from './src/core/engine/ScreenerEngine.js';
import { FinanceReporter } from './src/reporters/FinanceReporter.js';
async function main() {
// ── 1. Load portfolio ──────────────────────────────────────────────────────
if (!existsSync('./portfolio.json')) {
console.error('portfolio.json not found. Edit it with your holdings and re-run.');
process.exit(1);
}
const { holdings } = JSON.parse(readFileSync('./portfolio.json', 'utf8'));
const byType = holdings.reduce((acc, h) => {
const t = h.type ?? 'stock';
acc[t] = (acc[t] ?? 0) + 1;
return acc;
}, {});
console.log(
`📋 Portfolio: ${holdings.length} positions — ${Object.entries(byType)
.map(([t, n]) => `${n} ${t}`)
.join(', ')}\n`,
);
// ── 2. Fetch SimpleFIN data ────────────────────────────────────────────────
let personalFinance = null;
if (process.env.SIMPLEFIN_ACCESS_URL || process.env.SIMPLEFIN_SETUP_TOKEN) {
try {
process.stdout.write('💰 Fetching accounts from SimpleFIN...');
const client = new SimpleFINClient();
await client.init();
const { accounts } = await client.getAccounts();
personalFinance = new PersonalFinanceAnalyzer().analyse(accounts);
process.stdout.write(` ${accounts.length} accounts loaded\n`);
} catch (err) {
process.stdout.write(` skipped — ${err.message}\n`);
}
} else {
console.log(
' SimpleFIN not configured — add SIMPLEFIN_SETUP_TOKEN to .env for account data\n',
);
}
// ── 3. Screen stocks + ETFs (crypto handled separately by PortfolioAdvisor) ─
const screenableTickers = holdings
.filter((h) => (h.type ?? 'stock') !== 'crypto')
.map((h) => h.ticker.toUpperCase());
let results = { STOCK: [], ETF: [], BOND: [], ERROR: [], marketContext: {} };
if (screenableTickers.length > 0) {
process.stdout.write(`📊 Screening ${screenableTickers.length} stock/ETF positions...`);
results = await new ScreenerEngine().screenTickers(screenableTickers);
process.stdout.write(' done\n');
}
// ── 4. Fetch crypto prices + generate advice ───────────────────────────────
process.stdout.write('💡 Generating advice...');
const advice = await new PortfolioAdvisor().advise(holdings, results);
process.stdout.write(' done\n');
// ── 5. Generate report ─────────────────────────────────────────────────────
const reportPath = new FinanceReporter().generate(advice, personalFinance, results.marketContext);
console.log(`\n✅ Finance report saved to: ${reportPath}\n`);
}
main().catch((err) => console.error('Failed:', err.message));