ray-finance 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -29
- package/dist/cli/index.js +23 -0
- package/dist/demo/data.d.ts +252 -0
- package/dist/demo/data.js +272 -0
- package/dist/demo/logos.d.ts +8 -0
- package/dist/demo/logos.js +58 -0
- package/dist/demo/seed.d.ts +1 -0
- package/dist/demo/seed.js +125 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src=".github/ray-logo.png" alt="Ray" width="
|
|
2
|
+
<img src=".github/ray-logo.png" alt="Ray" width="108" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -14,40 +14,17 @@
|
|
|
14
14
|
|
|
15
15
|
<br />
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
net worth $45,230 +$120
|
|
21
|
-
|
|
22
|
-
spending $2,340 this month · $340 less vs last month
|
|
23
|
-
Dining -$114 · Shopping -$142 · Groceries -$73
|
|
24
|
-
|
|
25
|
-
▓▓▓▓▓▓▓░ Dining 92%
|
|
26
|
-
|
|
27
|
-
▓▓▓▓░░░░ Emergency fund $18,200/$40,000
|
|
28
|
-
|
|
29
|
-
upcoming Netflix $16 in 3d · Comcast $142 in 6d
|
|
30
|
-
|
|
31
|
-
score 72/100 · 5d no dining · 3d on pace
|
|
32
|
-
|
|
33
|
-
──────────────────────────────────────────────────
|
|
34
|
-
|
|
35
|
-
❯ if I quit my job to freelance, how long can I survive?
|
|
36
|
-
|
|
37
|
-
Based on your last 3 months: you burn $4,820/mo after
|
|
38
|
-
fixed costs. With $18,200 in savings, that's
|
|
39
|
-
3.8 months of runway at current spend.
|
|
40
|
-
|
|
41
|
-
Cut dining and shopping to last-month levels and
|
|
42
|
-
you stretch to 5.1 months. Land one $8k contract
|
|
43
|
-
in that window and you never dip below $10k.
|
|
44
|
-
```
|
|
17
|
+
<p align="center">
|
|
18
|
+
<img src=".github/ray-demo.png" alt="Ray demo" />
|
|
19
|
+
</p>
|
|
45
20
|
|
|
46
21
|
Open Ray and it shows your net worth, spending vs last month, budget pacing, and upcoming bills — before you type a word. Ask a question and it answers from your real data, not guesses. Local-first. Encrypted. Open source.
|
|
47
22
|
|
|
48
23
|
## Features
|
|
49
24
|
|
|
50
25
|
- **It already knows** — Every conversation starts with a real-time financial briefing. Net worth, spending velocity, budget alerts, goal pace, upcoming bills, and your daily score. No "let me look that up."
|
|
26
|
+
- **Persistent context** — Ray maintains a financial profile that evolves with you: income, goals, strategy, key decisions, and open items. It updates this context as your situation changes, so every conversation picks up where the last one left off.
|
|
27
|
+
- **Long-term memory** — Important details from conversations are saved as memories. Mention you're saving for a house or switching jobs and Ray remembers — without you repeating yourself.
|
|
51
28
|
- **Bank sync via Plaid** — Connect checking, savings, credit cards, investments, and loans
|
|
52
29
|
- **Encrypted local database** — All data stays on your machine in an AES-256 encrypted SQLite database
|
|
53
30
|
- **Daily scoring** — A 0-100 behavior score with streaks and 14 unlockable achievements. No restaurants for a week? That's Kitchen Hero. Five zero-spend days? Monk Mode.
|
|
@@ -65,6 +42,26 @@ Open Ray and it shows your net worth, spending vs last month, budget pacing, and
|
|
|
65
42
|
npm install -g ray-finance
|
|
66
43
|
```
|
|
67
44
|
|
|
45
|
+
## Try It
|
|
46
|
+
|
|
47
|
+
Explore Ray with realistic fake data — no bank accounts needed.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
ray demo # seed a demo database
|
|
51
|
+
ray --demo status # financial overview
|
|
52
|
+
ray --demo accounts # linked accounts with balances
|
|
53
|
+
ray --demo spending # spending breakdown by category
|
|
54
|
+
ray --demo budgets # budget tracking
|
|
55
|
+
ray --demo goals # financial goal progress
|
|
56
|
+
ray --demo score # daily score, streaks, achievements
|
|
57
|
+
ray --demo alerts # financial alerts
|
|
58
|
+
ray --demo transactions # recent transactions
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The dashboard commands work with no setup at all. To also try the AI chat with demo data, run `ray setup` first and add an [Anthropic API key](https://console.anthropic.com) or Ray API key — then `ray --demo` will start an interactive session where you can ask questions about the fake portfolio.
|
|
62
|
+
|
|
63
|
+
When you're ready to connect real accounts, run `ray link`.
|
|
64
|
+
|
|
68
65
|
## Quick Start
|
|
69
66
|
|
|
70
67
|
```bash
|
|
@@ -98,6 +95,8 @@ Run `ray --help` to see all available commands.
|
|
|
98
95
|
| Command | Description |
|
|
99
96
|
|---------|-------------|
|
|
100
97
|
| `ray` | Interactive AI chat with your financial advisor |
|
|
98
|
+
| `ray demo` | Seed a demo database with realistic fake data |
|
|
99
|
+
| `ray --demo <cmd>` | Run any command against demo data |
|
|
101
100
|
| `ray setup` | Configure API keys and preferences |
|
|
102
101
|
| `ray link` | Connect a new bank account |
|
|
103
102
|
| `ray sync` | Pull latest transactions and balances |
|
|
@@ -155,6 +154,7 @@ Ray stores everything in `~/.ray/`:
|
|
|
155
154
|
context.md # Persistent financial context for AI
|
|
156
155
|
data/
|
|
157
156
|
finance.db # Encrypted SQLite database
|
|
157
|
+
demo.db # Demo database (created by `ray demo`)
|
|
158
158
|
sync.log # Daily sync output
|
|
159
159
|
```
|
|
160
160
|
|
|
@@ -174,6 +174,7 @@ RAY_API_KEY= # Ray API key (managed mode, replaces the above)
|
|
|
174
174
|
|
|
175
175
|
## Roadmap
|
|
176
176
|
|
|
177
|
+
- [ ] Bring your own model — use any LLM provider (OpenAI, Ollama, open-source models, etc.)
|
|
177
178
|
- [ ] Daily digest email — morning summary of your finances
|
|
178
179
|
|
|
179
180
|
Have an idea? [Open a PR](https://github.com/cdinnison/ray-finance/pulls).
|
package/dist/cli/index.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
// --demo flag: use dedicated demo database (must run before config import)
|
|
5
|
+
const isDemoMode = process.argv.includes("--demo");
|
|
6
|
+
if (isDemoMode) {
|
|
7
|
+
process.argv = process.argv.filter(a => a !== "--demo");
|
|
8
|
+
}
|
|
2
9
|
import { Command } from "commander";
|
|
3
10
|
import { createRequire } from "module";
|
|
4
11
|
import { config, isConfigured, useManaged, RAY_PROXY_BASE } from "../config.js";
|
|
5
12
|
import { helpScreen } from "./format.js";
|
|
13
|
+
// Override config for demo mode (demo DB is unencrypted)
|
|
14
|
+
if (isDemoMode) {
|
|
15
|
+
config.dbPath = resolve(homedir(), ".ray", "data", "demo.db");
|
|
16
|
+
config.dbEncryptionKey = "";
|
|
17
|
+
}
|
|
6
18
|
const require = createRequire(import.meta.url);
|
|
7
19
|
const { version } = require("../../package.json");
|
|
8
20
|
const program = new Command();
|
|
@@ -181,6 +193,14 @@ program
|
|
|
181
193
|
const { runDoctor } = await import("./doctor.js");
|
|
182
194
|
await runDoctor();
|
|
183
195
|
});
|
|
196
|
+
program
|
|
197
|
+
.command("demo")
|
|
198
|
+
.description("Seed a demo database with realistic fake data")
|
|
199
|
+
.action(async () => {
|
|
200
|
+
const demoPath = resolve(homedir(), ".ray", "data", "demo.db");
|
|
201
|
+
const { seedDemoDb } = await import("../demo/seed.js");
|
|
202
|
+
seedDemoDb(demoPath);
|
|
203
|
+
});
|
|
184
204
|
program
|
|
185
205
|
.command("completions")
|
|
186
206
|
.description("Install shell completions")
|
|
@@ -189,6 +209,8 @@ program
|
|
|
189
209
|
installCompletions();
|
|
190
210
|
});
|
|
191
211
|
function ensureConfigured() {
|
|
212
|
+
if (isDemoMode)
|
|
213
|
+
return;
|
|
192
214
|
if (!isConfigured()) {
|
|
193
215
|
console.error("Ray is not configured. Run 'ray setup' first.");
|
|
194
216
|
process.exit(1);
|
|
@@ -213,6 +235,7 @@ program.configureHelp({
|
|
|
213
235
|
{ name: "billing", desc: "Manage your Ray subscription" },
|
|
214
236
|
{ name: "update", desc: "Update Ray to the latest version" },
|
|
215
237
|
{ name: "doctor", desc: "Check system health" },
|
|
238
|
+
{ name: "demo", desc: "Seed a demo database with fake data" },
|
|
216
239
|
{ name: "completions", desc: "Install shell completions" },
|
|
217
240
|
]),
|
|
218
241
|
});
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
export declare const institutions: {
|
|
2
|
+
item_id: string;
|
|
3
|
+
access_token: string;
|
|
4
|
+
name: string;
|
|
5
|
+
products: string;
|
|
6
|
+
logo: string;
|
|
7
|
+
primary_color: string;
|
|
8
|
+
}[];
|
|
9
|
+
export declare const accounts: ({
|
|
10
|
+
account_id: string;
|
|
11
|
+
item_id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
official_name: string;
|
|
14
|
+
type: string;
|
|
15
|
+
subtype: string;
|
|
16
|
+
mask: string;
|
|
17
|
+
current_balance: number;
|
|
18
|
+
available_balance: number;
|
|
19
|
+
currency: string;
|
|
20
|
+
balance_limit: null;
|
|
21
|
+
} | {
|
|
22
|
+
account_id: string;
|
|
23
|
+
item_id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
official_name: string;
|
|
26
|
+
type: string;
|
|
27
|
+
subtype: string;
|
|
28
|
+
mask: string;
|
|
29
|
+
current_balance: number;
|
|
30
|
+
available_balance: null;
|
|
31
|
+
currency: string;
|
|
32
|
+
balance_limit: null;
|
|
33
|
+
} | {
|
|
34
|
+
account_id: string;
|
|
35
|
+
item_id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
official_name: string;
|
|
38
|
+
type: string;
|
|
39
|
+
subtype: string;
|
|
40
|
+
mask: string;
|
|
41
|
+
current_balance: number;
|
|
42
|
+
available_balance: number;
|
|
43
|
+
currency: string;
|
|
44
|
+
balance_limit: number;
|
|
45
|
+
} | {
|
|
46
|
+
account_id: string;
|
|
47
|
+
item_id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
official_name: null;
|
|
50
|
+
type: string;
|
|
51
|
+
subtype: string;
|
|
52
|
+
mask: null;
|
|
53
|
+
current_balance: number;
|
|
54
|
+
available_balance: null;
|
|
55
|
+
currency: string;
|
|
56
|
+
balance_limit: null;
|
|
57
|
+
})[];
|
|
58
|
+
export declare const transactions: ({
|
|
59
|
+
transaction_id: string;
|
|
60
|
+
account_id: string;
|
|
61
|
+
amount: number;
|
|
62
|
+
date: string;
|
|
63
|
+
name: string;
|
|
64
|
+
merchant_name: string;
|
|
65
|
+
category: string;
|
|
66
|
+
subcategory: string;
|
|
67
|
+
pending: number;
|
|
68
|
+
payment_channel: string;
|
|
69
|
+
} | {
|
|
70
|
+
transaction_id: string;
|
|
71
|
+
account_id: string;
|
|
72
|
+
amount: number;
|
|
73
|
+
date: string;
|
|
74
|
+
name: string;
|
|
75
|
+
merchant_name: null;
|
|
76
|
+
category: string;
|
|
77
|
+
subcategory: string;
|
|
78
|
+
pending: number;
|
|
79
|
+
payment_channel: string;
|
|
80
|
+
})[];
|
|
81
|
+
export declare const securities: {
|
|
82
|
+
security_id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
ticker: string;
|
|
85
|
+
type: string;
|
|
86
|
+
close_price: number;
|
|
87
|
+
close_price_as_of: string;
|
|
88
|
+
}[];
|
|
89
|
+
export declare const holdings: ({
|
|
90
|
+
account_id: string;
|
|
91
|
+
security_id: string;
|
|
92
|
+
quantity: number;
|
|
93
|
+
value: number;
|
|
94
|
+
cost_basis: number;
|
|
95
|
+
price: number;
|
|
96
|
+
price_as_of: string;
|
|
97
|
+
vested_value: null;
|
|
98
|
+
vested_quantity: null;
|
|
99
|
+
} | {
|
|
100
|
+
account_id: string;
|
|
101
|
+
security_id: string;
|
|
102
|
+
quantity: number;
|
|
103
|
+
value: number;
|
|
104
|
+
cost_basis: number;
|
|
105
|
+
price: number;
|
|
106
|
+
price_as_of: string;
|
|
107
|
+
vested_value: number;
|
|
108
|
+
vested_quantity: number;
|
|
109
|
+
})[];
|
|
110
|
+
export declare const liabilities: ({
|
|
111
|
+
account_id: string;
|
|
112
|
+
type: string;
|
|
113
|
+
interest_rate: number;
|
|
114
|
+
origination_date: null;
|
|
115
|
+
original_balance: null;
|
|
116
|
+
current_balance: number;
|
|
117
|
+
minimum_payment: number;
|
|
118
|
+
next_payment_due: string;
|
|
119
|
+
last_payment_amount: number;
|
|
120
|
+
last_payment_date: string;
|
|
121
|
+
credit_limit: number;
|
|
122
|
+
last_statement_issue_date: string;
|
|
123
|
+
is_overdue: number;
|
|
124
|
+
apr_type: string;
|
|
125
|
+
maturity_date: null;
|
|
126
|
+
loan_type: null;
|
|
127
|
+
property_address: null;
|
|
128
|
+
escrow_balance: null;
|
|
129
|
+
loan_status: null;
|
|
130
|
+
loan_name: null;
|
|
131
|
+
repayment_plan: null;
|
|
132
|
+
expected_payoff_date: null;
|
|
133
|
+
ytd_interest_paid: null;
|
|
134
|
+
ytd_principal_paid: null;
|
|
135
|
+
} | {
|
|
136
|
+
account_id: string;
|
|
137
|
+
type: string;
|
|
138
|
+
interest_rate: number;
|
|
139
|
+
origination_date: string;
|
|
140
|
+
original_balance: number;
|
|
141
|
+
current_balance: number;
|
|
142
|
+
minimum_payment: number;
|
|
143
|
+
next_payment_due: string;
|
|
144
|
+
last_payment_amount: number;
|
|
145
|
+
last_payment_date: string;
|
|
146
|
+
credit_limit: null;
|
|
147
|
+
last_statement_issue_date: null;
|
|
148
|
+
is_overdue: number;
|
|
149
|
+
apr_type: string;
|
|
150
|
+
maturity_date: string;
|
|
151
|
+
loan_type: string;
|
|
152
|
+
property_address: string;
|
|
153
|
+
escrow_balance: number;
|
|
154
|
+
loan_status: string;
|
|
155
|
+
loan_name: string;
|
|
156
|
+
repayment_plan: null;
|
|
157
|
+
expected_payoff_date: string;
|
|
158
|
+
ytd_interest_paid: number;
|
|
159
|
+
ytd_principal_paid: number;
|
|
160
|
+
})[];
|
|
161
|
+
export declare const recurring: ({
|
|
162
|
+
stream_id: string;
|
|
163
|
+
account_id: string;
|
|
164
|
+
merchant_name: string;
|
|
165
|
+
description: string;
|
|
166
|
+
frequency: string;
|
|
167
|
+
category: string;
|
|
168
|
+
subcategory: string;
|
|
169
|
+
avg_amount: number;
|
|
170
|
+
last_amount: number;
|
|
171
|
+
first_date: string;
|
|
172
|
+
last_date: string;
|
|
173
|
+
is_active: number;
|
|
174
|
+
status: string;
|
|
175
|
+
stream_type: string;
|
|
176
|
+
} | {
|
|
177
|
+
stream_id: string;
|
|
178
|
+
account_id: string;
|
|
179
|
+
merchant_name: null;
|
|
180
|
+
description: string;
|
|
181
|
+
frequency: string;
|
|
182
|
+
category: string;
|
|
183
|
+
subcategory: string;
|
|
184
|
+
avg_amount: number;
|
|
185
|
+
last_amount: number;
|
|
186
|
+
first_date: string;
|
|
187
|
+
last_date: string;
|
|
188
|
+
is_active: number;
|
|
189
|
+
status: string;
|
|
190
|
+
stream_type: string;
|
|
191
|
+
})[];
|
|
192
|
+
export declare const budgets: {
|
|
193
|
+
category: string;
|
|
194
|
+
monthly_limit: number;
|
|
195
|
+
period: string;
|
|
196
|
+
}[];
|
|
197
|
+
export declare const goals: {
|
|
198
|
+
name: string;
|
|
199
|
+
target_amount: number;
|
|
200
|
+
current_amount: number;
|
|
201
|
+
target_date: string;
|
|
202
|
+
status: string;
|
|
203
|
+
}[];
|
|
204
|
+
export declare const dailyScores: {
|
|
205
|
+
date: string;
|
|
206
|
+
score: number;
|
|
207
|
+
restaurant_count: number;
|
|
208
|
+
shopping_count: number;
|
|
209
|
+
food_spend: number;
|
|
210
|
+
total_spend: number;
|
|
211
|
+
zero_spend: number;
|
|
212
|
+
no_restaurant_streak: number;
|
|
213
|
+
no_shopping_streak: number;
|
|
214
|
+
on_pace_streak: number;
|
|
215
|
+
}[];
|
|
216
|
+
export declare const achievements: {
|
|
217
|
+
key: string;
|
|
218
|
+
name: string;
|
|
219
|
+
description: string;
|
|
220
|
+
unlocked_at: string;
|
|
221
|
+
}[];
|
|
222
|
+
export declare const netWorthHistory: {
|
|
223
|
+
date: string;
|
|
224
|
+
total_assets: number;
|
|
225
|
+
total_liabilities: number;
|
|
226
|
+
net_worth: number;
|
|
227
|
+
}[];
|
|
228
|
+
export declare const investmentTransactions: {
|
|
229
|
+
investment_transaction_id: string;
|
|
230
|
+
account_id: string;
|
|
231
|
+
security_id: string;
|
|
232
|
+
date: string;
|
|
233
|
+
name: string;
|
|
234
|
+
quantity: number;
|
|
235
|
+
amount: number;
|
|
236
|
+
price: number;
|
|
237
|
+
fees: number;
|
|
238
|
+
type: string;
|
|
239
|
+
subtype: string;
|
|
240
|
+
iso_currency_code: string;
|
|
241
|
+
}[];
|
|
242
|
+
export declare const recurringBills: {
|
|
243
|
+
name: string;
|
|
244
|
+
amount: number;
|
|
245
|
+
day_of_month: number;
|
|
246
|
+
type: string;
|
|
247
|
+
account_id: string;
|
|
248
|
+
}[];
|
|
249
|
+
export declare const memories: {
|
|
250
|
+
content: string;
|
|
251
|
+
category: string;
|
|
252
|
+
}[];
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { LOGOS } from "./logos.js";
|
|
2
|
+
// ─── Date Helpers ─── //
|
|
3
|
+
const now = new Date();
|
|
4
|
+
function today() {
|
|
5
|
+
return now.toISOString().slice(0, 10);
|
|
6
|
+
}
|
|
7
|
+
function yesterday() {
|
|
8
|
+
return daysAgo(1);
|
|
9
|
+
}
|
|
10
|
+
function daysAgo(n) {
|
|
11
|
+
const d = new Date(now);
|
|
12
|
+
d.setDate(d.getDate() - n);
|
|
13
|
+
return d.toISOString().slice(0, 10);
|
|
14
|
+
}
|
|
15
|
+
/** First of the month, N months ago (0 = this month) */
|
|
16
|
+
function monthStart(monthsAgo) {
|
|
17
|
+
const d = new Date(now.getFullYear(), now.getMonth() - monthsAgo, 1);
|
|
18
|
+
return d.toISOString().slice(0, 10);
|
|
19
|
+
}
|
|
20
|
+
/** Specific day of the month, N months ago. For current month (0), clamps to today. */
|
|
21
|
+
function monthDay(monthsAgo, day) {
|
|
22
|
+
const d = new Date(now.getFullYear(), now.getMonth() - monthsAgo, day);
|
|
23
|
+
if (monthsAgo === 0 && d > now) {
|
|
24
|
+
// Clamp future current-month dates to recent past
|
|
25
|
+
return daysAgo(Math.max(1, day % (now.getDate()) + 1));
|
|
26
|
+
}
|
|
27
|
+
return d.toISOString().slice(0, 10);
|
|
28
|
+
}
|
|
29
|
+
/** Date N months from now */
|
|
30
|
+
function monthsFromNow(n) {
|
|
31
|
+
const d = new Date(now.getFullYear(), now.getMonth() + n, now.getDate());
|
|
32
|
+
return d.toISOString().slice(0, 10);
|
|
33
|
+
}
|
|
34
|
+
/** Date N years ago */
|
|
35
|
+
function yearsAgo(n) {
|
|
36
|
+
const d = new Date(now.getFullYear() - n, now.getMonth(), now.getDate());
|
|
37
|
+
return d.toISOString().slice(0, 10);
|
|
38
|
+
}
|
|
39
|
+
// ─── Institutions ─── //
|
|
40
|
+
export const institutions = [
|
|
41
|
+
{ item_id: "demo-chase", access_token: "demo-access-chase", name: "Chase", products: '["transactions","liabilities"]', logo: LOGOS.chase, primary_color: "#003087" },
|
|
42
|
+
{ item_id: "demo-robinhood", access_token: "demo-access-robinhood", name: "Robinhood", products: '["transactions","investments"]', logo: LOGOS.robinhood, primary_color: "#00C805" },
|
|
43
|
+
{ item_id: "demo-schwab", access_token: "demo-access-schwab", name: "Charles Schwab", products: '["transactions","investments"]', logo: LOGOS.schwab, primary_color: "#00A0DF" },
|
|
44
|
+
{ item_id: "demo-amex", access_token: "demo-access-amex", name: "American Express", products: '["transactions","liabilities"]', logo: LOGOS.amex, primary_color: "#006FCF" },
|
|
45
|
+
];
|
|
46
|
+
// ─── Accounts ─── //
|
|
47
|
+
export const accounts = [
|
|
48
|
+
{ account_id: "demo-chase-checking", item_id: "demo-chase", name: "Total Checking", official_name: "Chase Total Checking", type: "depository", subtype: "checking", mask: "4521", current_balance: 4200, available_balance: 4200, currency: "USD", balance_limit: null },
|
|
49
|
+
{ account_id: "demo-chase-savings", item_id: "demo-chase", name: "Chase Savings", official_name: "Chase Savings", type: "depository", subtype: "savings", mask: "7890", current_balance: 12500, available_balance: 12500, currency: "USD", balance_limit: null },
|
|
50
|
+
{ account_id: "demo-robinhood-brokerage", item_id: "demo-robinhood", name: "Individual", official_name: "Robinhood Individual", type: "investment", subtype: "brokerage", mask: "3344", current_balance: 34000, available_balance: null, currency: "USD", balance_limit: null },
|
|
51
|
+
{ account_id: "demo-schwab-401k", item_id: "demo-schwab", name: "401(k)", official_name: "Schwab 401(k)", type: "investment", subtype: "401k", mask: "9012", current_balance: 67000, available_balance: null, currency: "USD", balance_limit: null },
|
|
52
|
+
{ account_id: "demo-amex-gold", item_id: "demo-amex", name: "Gold Card", official_name: "American Express Gold Card", type: "credit", subtype: "credit card", mask: "1008", current_balance: 1830, available_balance: 13170, currency: "USD", balance_limit: 15000 },
|
|
53
|
+
{ account_id: "demo-chase-mortgage", item_id: "demo-chase", name: "Home Mortgage", official_name: "Chase Home Mortgage", type: "loan", subtype: "mortgage", mask: "6601", current_balance: 285000, available_balance: null, currency: "USD", balance_limit: null },
|
|
54
|
+
{ account_id: "demo-chase-home", item_id: "demo-chase", name: "Primary Residence", official_name: null, type: "other", subtype: "property", mask: null, current_balance: 425000, available_balance: null, currency: "USD", balance_limit: null },
|
|
55
|
+
];
|
|
56
|
+
// ─── Transactions ─── //
|
|
57
|
+
let txCounter = 0;
|
|
58
|
+
function txId() {
|
|
59
|
+
return `demo-tx-${++txCounter}`;
|
|
60
|
+
}
|
|
61
|
+
const CHK = "demo-chase-checking";
|
|
62
|
+
const AMEX = "demo-amex-gold";
|
|
63
|
+
export const transactions = [
|
|
64
|
+
// ── Income (negative = inflow in Plaid) ──
|
|
65
|
+
{ transaction_id: txId(), account_id: CHK, amount: -4250, date: monthDay(0, 1), name: "Payroll - Acme Corp", merchant_name: "Acme Corp", category: "INCOME", subcategory: "INCOME_WAGES", pending: 0, payment_channel: "online" },
|
|
66
|
+
{ transaction_id: txId(), account_id: CHK, amount: -4250, date: monthDay(0, 15), name: "Payroll - Acme Corp", merchant_name: "Acme Corp", category: "INCOME", subcategory: "INCOME_WAGES", pending: 0, payment_channel: "online" },
|
|
67
|
+
{ transaction_id: txId(), account_id: CHK, amount: -4250, date: monthDay(1, 1), name: "Payroll - Acme Corp", merchant_name: "Acme Corp", category: "INCOME", subcategory: "INCOME_WAGES", pending: 0, payment_channel: "online" },
|
|
68
|
+
{ transaction_id: txId(), account_id: CHK, amount: -4250, date: monthDay(1, 15), name: "Payroll - Acme Corp", merchant_name: "Acme Corp", category: "INCOME", subcategory: "INCOME_WAGES", pending: 0, payment_channel: "online" },
|
|
69
|
+
{ transaction_id: txId(), account_id: CHK, amount: -4250, date: monthDay(2, 1), name: "Payroll - Acme Corp", merchant_name: "Acme Corp", category: "INCOME", subcategory: "INCOME_WAGES", pending: 0, payment_channel: "online" },
|
|
70
|
+
{ transaction_id: txId(), account_id: CHK, amount: -4250, date: monthDay(2, 15), name: "Payroll - Acme Corp", merchant_name: "Acme Corp", category: "INCOME", subcategory: "INCOME_WAGES", pending: 0, payment_channel: "online" },
|
|
71
|
+
{ transaction_id: txId(), account_id: CHK, amount: -800, date: monthDay(1, 20), name: "Wire Transfer - Client LLC", merchant_name: "Client LLC", category: "INCOME", subcategory: "INCOME_OTHER", pending: 0, payment_channel: "online" },
|
|
72
|
+
// ── Rent (3 months) ──
|
|
73
|
+
{ transaction_id: txId(), account_id: CHK, amount: 1850, date: monthDay(0, 1), name: "Rent Payment", merchant_name: null, category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_RENT", pending: 0, payment_channel: "online" },
|
|
74
|
+
{ transaction_id: txId(), account_id: CHK, amount: 1850, date: monthDay(1, 1), name: "Rent Payment", merchant_name: null, category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_RENT", pending: 0, payment_channel: "online" },
|
|
75
|
+
{ transaction_id: txId(), account_id: CHK, amount: 1850, date: monthDay(2, 1), name: "Rent Payment", merchant_name: null, category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_RENT", pending: 0, payment_channel: "online" },
|
|
76
|
+
// ── Utilities ──
|
|
77
|
+
{ transaction_id: txId(), account_id: CHK, amount: 138.42, date: monthDay(0, 15), name: "PG&E", merchant_name: "PG&E", category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_GAS_AND_ELECTRICITY", pending: 0, payment_channel: "online" },
|
|
78
|
+
{ transaction_id: txId(), account_id: CHK, amount: 125.80, date: monthDay(1, 15), name: "PG&E", merchant_name: "PG&E", category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_GAS_AND_ELECTRICITY", pending: 0, payment_channel: "online" },
|
|
79
|
+
{ transaction_id: txId(), account_id: CHK, amount: 142.15, date: monthDay(2, 15), name: "PG&E", merchant_name: "PG&E", category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_GAS_AND_ELECTRICITY", pending: 0, payment_channel: "online" },
|
|
80
|
+
{ transaction_id: txId(), account_id: CHK, amount: 79.99, date: monthDay(0, 12), name: "Comcast Internet", merchant_name: "Comcast", category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_INTERNET_AND_CABLE", pending: 0, payment_channel: "online" },
|
|
81
|
+
{ transaction_id: txId(), account_id: CHK, amount: 79.99, date: monthDay(1, 12), name: "Comcast Internet", merchant_name: "Comcast", category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_INTERNET_AND_CABLE", pending: 0, payment_channel: "online" },
|
|
82
|
+
{ transaction_id: txId(), account_id: CHK, amount: 79.99, date: monthDay(2, 12), name: "Comcast Internet", merchant_name: "Comcast", category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_INTERNET_AND_CABLE", pending: 0, payment_channel: "online" },
|
|
83
|
+
// ── Food & Drink (this month — targeting ~$450 spend) ──
|
|
84
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 89.42, date: monthDay(0, 3), name: "Whole Foods Market", merchant_name: "Whole Foods", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_GROCERIES", pending: 0, payment_channel: "in store" },
|
|
85
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 52.18, date: monthDay(0, 7), name: "Trader Joe's", merchant_name: "Trader Joe's", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_GROCERIES", pending: 0, payment_channel: "in store" },
|
|
86
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 34.67, date: monthDay(0, 14), name: "Safeway", merchant_name: "Safeway", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_GROCERIES", pending: 0, payment_channel: "in store" },
|
|
87
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 67.30, date: monthDay(0, 5), name: "Olive Garden", merchant_name: "Olive Garden", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_RESTAURANTS", pending: 0, payment_channel: "in store" },
|
|
88
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 42.80, date: monthDay(0, 10), name: "Thai Kitchen", merchant_name: "Thai Kitchen", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_RESTAURANTS", pending: 0, payment_channel: "in store" },
|
|
89
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 12.45, date: monthDay(0, 8), name: "Chipotle", merchant_name: "Chipotle", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_FAST_FOOD", pending: 0, payment_channel: "in store" },
|
|
90
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 6.50, date: yesterday(), name: "Starbucks", merchant_name: "Starbucks", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_COFFEE", pending: 0, payment_channel: "in store" },
|
|
91
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 5.75, date: today(), name: "Blue Bottle Coffee", merchant_name: "Blue Bottle", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_COFFEE", pending: 0, payment_channel: "in store" },
|
|
92
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 78.50, date: monthDay(0, 18), name: "Whole Foods Market", merchant_name: "Whole Foods", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_GROCERIES", pending: 0, payment_channel: "in store" },
|
|
93
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 58.35, date: daysAgo(3), name: "Sushi Roku", merchant_name: "Sushi Roku", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_RESTAURANTS", pending: 0, payment_channel: "in store" },
|
|
94
|
+
// ── Food & Drink (last month) ──
|
|
95
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 92.10, date: monthDay(1, 5), name: "Whole Foods Market", merchant_name: "Whole Foods", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_GROCERIES", pending: 0, payment_channel: "in store" },
|
|
96
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 48.75, date: monthDay(1, 12), name: "Trader Joe's", merchant_name: "Trader Joe's", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_GROCERIES", pending: 0, payment_channel: "in store" },
|
|
97
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 55.20, date: monthDay(1, 18), name: "Nobu", merchant_name: "Nobu", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_RESTAURANTS", pending: 0, payment_channel: "in store" },
|
|
98
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 14.30, date: monthDay(1, 22), name: "Chipotle", merchant_name: "Chipotle", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_FAST_FOOD", pending: 0, payment_channel: "in store" },
|
|
99
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 6.50, date: monthDay(1, 8), name: "Starbucks", merchant_name: "Starbucks", category: "FOOD_AND_DRINK", subcategory: "FOOD_AND_DRINK_COFFEE", pending: 0, payment_channel: "in store" },
|
|
100
|
+
// ── Shopping (this month — large Apple purchase triggers alert) ──
|
|
101
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 34.99, date: daysAgo(12), name: "Amazon", merchant_name: "Amazon", category: "GENERAL_MERCHANDISE", subcategory: "GENERAL_MERCHANDISE_ONLINE_MARKETPLACES", pending: 0, payment_channel: "online" },
|
|
102
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 67.23, date: daysAgo(8), name: "Target", merchant_name: "Target", category: "GENERAL_MERCHANDISE", subcategory: "GENERAL_MERCHANDISE_SUPERSTORES", pending: 0, payment_channel: "in store" },
|
|
103
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 29.99, date: daysAgo(4), name: "Uniqlo", merchant_name: "Uniqlo", category: "GENERAL_MERCHANDISE", subcategory: "GENERAL_MERCHANDISE_CLOTHING_AND_ACCESSORIES", pending: 0, payment_channel: "in store" },
|
|
104
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 849.99, date: today(), name: "Apple Store", merchant_name: "Apple", category: "GENERAL_MERCHANDISE", subcategory: "GENERAL_MERCHANDISE_ELECTRONICS", pending: 0, payment_channel: "in store" },
|
|
105
|
+
// ── Shopping (last month) ──
|
|
106
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 299.99, date: monthDay(1, 10), name: "Best Buy", merchant_name: "Best Buy", category: "GENERAL_MERCHANDISE", subcategory: "GENERAL_MERCHANDISE_ELECTRONICS", pending: 0, payment_channel: "in store" },
|
|
107
|
+
{ transaction_id: txId(), account_id: AMEX, amount: 42.50, date: monthDay(1, 16), name: "Amazon", merchant_name: "Amazon", category: "GENERAL_MERCHANDISE", subcategory: "GENERAL_MERCHANDISE_ONLINE_MARKETPLACES", pending: 0, payment_channel: "online" },
|
|
108
|
+
// ── Transportation (this month — targeting ~$100) ──
|
|
109
|
+
{ transaction_id: txId(), account_id: CHK, amount: 52.40, date: monthDay(0, 6), name: "Shell", merchant_name: "Shell", category: "TRANSPORTATION", subcategory: "TRANSPORTATION_GAS", pending: 0, payment_channel: "in store" },
|
|
110
|
+
{ transaction_id: txId(), account_id: CHK, amount: 18.75, date: monthDay(0, 11), name: "Uber", merchant_name: "Uber", category: "TRANSPORTATION", subcategory: "TRANSPORTATION_TAXIS_AND_RIDE_SHARES", pending: 0, payment_channel: "online" },
|
|
111
|
+
{ transaction_id: txId(), account_id: CHK, amount: 24.30, date: yesterday(), name: "Uber", merchant_name: "Uber", category: "TRANSPORTATION", subcategory: "TRANSPORTATION_TAXIS_AND_RIDE_SHARES", pending: 0, payment_channel: "online" },
|
|
112
|
+
// ── Transportation (last month) ──
|
|
113
|
+
{ transaction_id: txId(), account_id: CHK, amount: 48.90, date: monthDay(1, 8), name: "Shell", merchant_name: "Shell", category: "TRANSPORTATION", subcategory: "TRANSPORTATION_GAS", pending: 0, payment_channel: "in store" },
|
|
114
|
+
{ transaction_id: txId(), account_id: CHK, amount: 35.00, date: monthDay(1, 14), name: "Clipper Card", merchant_name: "BART", category: "TRANSPORTATION", subcategory: "TRANSPORTATION_PUBLIC_TRANSIT", pending: 0, payment_channel: "online" },
|
|
115
|
+
{ transaction_id: txId(), account_id: CHK, amount: 22.15, date: monthDay(1, 25), name: "Uber", merchant_name: "Uber", category: "TRANSPORTATION", subcategory: "TRANSPORTATION_TAXIS_AND_RIDE_SHARES", pending: 0, payment_channel: "online" },
|
|
116
|
+
// ── Entertainment (this month — targeting ~$85) ──
|
|
117
|
+
{ transaction_id: txId(), account_id: CHK, amount: 15.99, date: monthDay(0, 2), name: "Netflix", merchant_name: "Netflix", category: "ENTERTAINMENT", subcategory: "ENTERTAINMENT_TV_AND_MOVIES", pending: 0, payment_channel: "online" },
|
|
118
|
+
{ transaction_id: txId(), account_id: CHK, amount: 10.99, date: monthDay(0, 2), name: "Spotify", merchant_name: "Spotify", category: "ENTERTAINMENT", subcategory: "ENTERTAINMENT_MUSIC_AND_AUDIO", pending: 0, payment_channel: "online" },
|
|
119
|
+
{ transaction_id: txId(), account_id: CHK, amount: 28.50, date: monthDay(0, 16), name: "AMC Theaters", merchant_name: "AMC", category: "ENTERTAINMENT", subcategory: "ENTERTAINMENT_TV_AND_MOVIES", pending: 0, payment_channel: "in store" },
|
|
120
|
+
{ transaction_id: txId(), account_id: CHK, amount: 49.99, date: daysAgo(5), name: "Steam", merchant_name: "Steam", category: "ENTERTAINMENT", subcategory: "ENTERTAINMENT_GAMES", pending: 0, payment_channel: "online" },
|
|
121
|
+
// ── Entertainment (last month) ──
|
|
122
|
+
{ transaction_id: txId(), account_id: CHK, amount: 15.99, date: monthDay(1, 2), name: "Netflix", merchant_name: "Netflix", category: "ENTERTAINMENT", subcategory: "ENTERTAINMENT_TV_AND_MOVIES", pending: 0, payment_channel: "online" },
|
|
123
|
+
{ transaction_id: txId(), account_id: CHK, amount: 10.99, date: monthDay(1, 2), name: "Spotify", merchant_name: "Spotify", category: "ENTERTAINMENT", subcategory: "ENTERTAINMENT_MUSIC_AND_AUDIO", pending: 0, payment_channel: "online" },
|
|
124
|
+
// ── Personal Care ──
|
|
125
|
+
{ transaction_id: txId(), account_id: CHK, amount: 35.00, date: monthDay(0, 17), name: "Supercuts", merchant_name: "Supercuts", category: "PERSONAL_CARE", subcategory: "PERSONAL_CARE_HAIR_AND_BEAUTY", pending: 0, payment_channel: "in store" },
|
|
126
|
+
{ transaction_id: txId(), account_id: CHK, amount: 22.47, date: daysAgo(2), name: "CVS Pharmacy", merchant_name: "CVS", category: "MEDICAL", subcategory: "MEDICAL_PHARMACIES_AND_SUPPLEMENTS", pending: 0, payment_channel: "in store" },
|
|
127
|
+
// ── Loan Payments ──
|
|
128
|
+
{ transaction_id: txId(), account_id: CHK, amount: 2100, date: monthDay(0, 1), name: "Chase Mortgage Payment", merchant_name: "Chase", category: "LOAN_PAYMENTS", subcategory: "LOAN_PAYMENTS_MORTGAGE_PAYMENT", pending: 0, payment_channel: "online" },
|
|
129
|
+
{ transaction_id: txId(), account_id: CHK, amount: 2100, date: monthDay(1, 1), name: "Chase Mortgage Payment", merchant_name: "Chase", category: "LOAN_PAYMENTS", subcategory: "LOAN_PAYMENTS_MORTGAGE_PAYMENT", pending: 0, payment_channel: "online" },
|
|
130
|
+
{ transaction_id: txId(), account_id: CHK, amount: 2100, date: monthDay(2, 1), name: "Chase Mortgage Payment", merchant_name: "Chase", category: "LOAN_PAYMENTS", subcategory: "LOAN_PAYMENTS_MORTGAGE_PAYMENT", pending: 0, payment_channel: "online" },
|
|
131
|
+
// ── Gym ──
|
|
132
|
+
{ transaction_id: txId(), account_id: CHK, amount: 95, date: monthDay(0, 5), name: "Equinox", merchant_name: "Equinox", category: "PERSONAL_CARE", subcategory: "PERSONAL_CARE_GYMS_AND_FITNESS_CENTERS", pending: 0, payment_channel: "online" },
|
|
133
|
+
{ transaction_id: txId(), account_id: CHK, amount: 95, date: monthDay(1, 5), name: "Equinox", merchant_name: "Equinox", category: "PERSONAL_CARE", subcategory: "PERSONAL_CARE_GYMS_AND_FITNESS_CENTERS", pending: 0, payment_channel: "online" },
|
|
134
|
+
// ── Car Insurance ──
|
|
135
|
+
{ transaction_id: txId(), account_id: CHK, amount: 145, date: monthDay(0, 20), name: "GEICO", merchant_name: "GEICO", category: "GENERAL_SERVICES", subcategory: "GENERAL_SERVICES_INSURANCE", pending: 0, payment_channel: "online" },
|
|
136
|
+
{ transaction_id: txId(), account_id: CHK, amount: 145, date: monthDay(1, 20), name: "GEICO", merchant_name: "GEICO", category: "GENERAL_SERVICES", subcategory: "GENERAL_SERVICES_INSURANCE", pending: 0, payment_channel: "online" },
|
|
137
|
+
// ── iCloud ──
|
|
138
|
+
{ transaction_id: txId(), account_id: CHK, amount: 2.99, date: monthDay(0, 3), name: "Apple iCloud", merchant_name: "Apple", category: "GENERAL_SERVICES", subcategory: "GENERAL_SERVICES_OTHER_GENERAL_SERVICES", pending: 0, payment_channel: "online" },
|
|
139
|
+
{ transaction_id: txId(), account_id: CHK, amount: 2.99, date: monthDay(1, 3), name: "Apple iCloud", merchant_name: "Apple", category: "GENERAL_SERVICES", subcategory: "GENERAL_SERVICES_OTHER_GENERAL_SERVICES", pending: 0, payment_channel: "online" },
|
|
140
|
+
];
|
|
141
|
+
// ─── Securities ─── //
|
|
142
|
+
export const securities = [
|
|
143
|
+
{ security_id: "demo-sec-aapl", name: "Apple Inc", ticker: "AAPL", type: "equity", close_price: 227.50, close_price_as_of: daysAgo(1) },
|
|
144
|
+
{ security_id: "demo-sec-tsla", name: "Tesla Inc", ticker: "TSLA", type: "equity", close_price: 172.30, close_price_as_of: daysAgo(1) },
|
|
145
|
+
{ security_id: "demo-sec-voo", name: "Vanguard S&P 500 ETF", ticker: "VOO", type: "etf", close_price: 532.80, close_price_as_of: daysAgo(1) },
|
|
146
|
+
{ security_id: "demo-sec-vtsax", name: "Vanguard Total Stock Market Index", ticker: "VTSAX", type: "mutual fund", close_price: 118.45, close_price_as_of: daysAgo(1) },
|
|
147
|
+
{ security_id: "demo-sec-vbtlx", name: "Vanguard Total Bond Market Index", ticker: "VBTLX", type: "mutual fund", close_price: 10.82, close_price_as_of: daysAgo(1) },
|
|
148
|
+
{ security_id: "demo-sec-vttvx", name: "Vanguard Target Retirement 2035", ticker: "VTTVX", type: "mutual fund", close_price: 28.15, close_price_as_of: daysAgo(1) },
|
|
149
|
+
];
|
|
150
|
+
// ─── Holdings ─── //
|
|
151
|
+
export const holdings = [
|
|
152
|
+
{ account_id: "demo-robinhood-brokerage", security_id: "demo-sec-aapl", quantity: 50, value: 11375, cost_basis: 8500, price: 227.50, price_as_of: daysAgo(1), vested_value: null, vested_quantity: null },
|
|
153
|
+
{ account_id: "demo-robinhood-brokerage", security_id: "demo-sec-tsla", quantity: 35, value: 6030.50, cost_basis: 7000, price: 172.30, price_as_of: daysAgo(1), vested_value: null, vested_quantity: null },
|
|
154
|
+
{ account_id: "demo-robinhood-brokerage", security_id: "demo-sec-voo", quantity: 31, value: 16516.80, cost_basis: 14200, price: 532.80, price_as_of: daysAgo(1), vested_value: null, vested_quantity: null },
|
|
155
|
+
{ account_id: "demo-schwab-401k", security_id: "demo-sec-vtsax", quantity: 280, value: 33166, cost_basis: 28000, price: 118.45, price_as_of: daysAgo(1), vested_value: 33166, vested_quantity: 280 },
|
|
156
|
+
{ account_id: "demo-schwab-401k", security_id: "demo-sec-vbtlx", quantity: 1200, value: 12984, cost_basis: 12500, price: 10.82, price_as_of: daysAgo(1), vested_value: 12984, vested_quantity: 1200 },
|
|
157
|
+
{ account_id: "demo-schwab-401k", security_id: "demo-sec-vttvx", quantity: 740, value: 20831, cost_basis: 18500, price: 28.15, price_as_of: daysAgo(1), vested_value: 20831, vested_quantity: 740 },
|
|
158
|
+
];
|
|
159
|
+
// ─── Liabilities ─── //
|
|
160
|
+
export const liabilities = [
|
|
161
|
+
{
|
|
162
|
+
account_id: "demo-amex-gold", type: "credit", interest_rate: 24.99, origination_date: null,
|
|
163
|
+
original_balance: null, current_balance: 1830, minimum_payment: 35,
|
|
164
|
+
next_payment_due: monthDay(0, 28), last_payment_amount: 500, last_payment_date: monthDay(1, 25),
|
|
165
|
+
credit_limit: 15000, last_statement_issue_date: monthDay(1, 28), is_overdue: 0,
|
|
166
|
+
apr_type: "variable", maturity_date: null, loan_type: null, property_address: null,
|
|
167
|
+
escrow_balance: null, loan_status: null, loan_name: null, repayment_plan: null,
|
|
168
|
+
expected_payoff_date: null, ytd_interest_paid: null, ytd_principal_paid: null,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
account_id: "demo-chase-mortgage", type: "mortgage", interest_rate: 6.875,
|
|
172
|
+
origination_date: yearsAgo(3), original_balance: 320000, current_balance: 285000,
|
|
173
|
+
minimum_payment: 2100, next_payment_due: monthDay(-1, 1),
|
|
174
|
+
last_payment_amount: 2100, last_payment_date: monthDay(0, 1),
|
|
175
|
+
credit_limit: null, last_statement_issue_date: null, is_overdue: 0,
|
|
176
|
+
apr_type: "fixed", maturity_date: monthsFromNow(27 * 12), loan_type: "conventional",
|
|
177
|
+
property_address: "123 Main St, San Francisco, CA 94102",
|
|
178
|
+
escrow_balance: 4200, loan_status: "active", loan_name: "30yr Fixed",
|
|
179
|
+
repayment_plan: null, expected_payoff_date: monthsFromNow(27 * 12),
|
|
180
|
+
ytd_interest_paid: 6125, ytd_principal_paid: 2175,
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
// ─── Recurring ─── //
|
|
184
|
+
export const recurring = [
|
|
185
|
+
{ stream_id: "demo-rec-salary", account_id: CHK, merchant_name: "Acme Corp", description: "Payroll - Acme Corp", frequency: "BIWEEKLY", category: "INCOME", subcategory: "INCOME_WAGES", avg_amount: -4250, last_amount: -4250, first_date: yearsAgo(2), last_date: monthDay(0, 15), is_active: 1, status: "MATURE", stream_type: "inflow" },
|
|
186
|
+
{ stream_id: "demo-rec-freelance", account_id: CHK, merchant_name: "Client LLC", description: "Wire Transfer - Client LLC", frequency: "MONTHLY", category: "INCOME", subcategory: "INCOME_OTHER", avg_amount: -800, last_amount: -800, first_date: yearsAgo(1), last_date: monthDay(1, 20), is_active: 1, status: "MATURE", stream_type: "inflow" },
|
|
187
|
+
{ stream_id: "demo-rec-rent", account_id: CHK, merchant_name: null, description: "Rent Payment", frequency: "MONTHLY", category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_RENT", avg_amount: 1850, last_amount: 1850, first_date: yearsAgo(2), last_date: monthDay(0, 1), is_active: 1, status: "MATURE", stream_type: "outflow" },
|
|
188
|
+
{ stream_id: "demo-rec-netflix", account_id: CHK, merchant_name: "Netflix", description: "Netflix", frequency: "MONTHLY", category: "ENTERTAINMENT", subcategory: "ENTERTAINMENT_TV_AND_MOVIES", avg_amount: 15.99, last_amount: 15.99, first_date: yearsAgo(3), last_date: monthDay(0, 2), is_active: 1, status: "MATURE", stream_type: "outflow" },
|
|
189
|
+
{ stream_id: "demo-rec-spotify", account_id: CHK, merchant_name: "Spotify", description: "Spotify Premium", frequency: "MONTHLY", category: "ENTERTAINMENT", subcategory: "ENTERTAINMENT_MUSIC_AND_AUDIO", avg_amount: 10.99, last_amount: 10.99, first_date: yearsAgo(2), last_date: monthDay(0, 2), is_active: 1, status: "MATURE", stream_type: "outflow" },
|
|
190
|
+
{ stream_id: "demo-rec-electric", account_id: CHK, merchant_name: "PG&E", description: "PG&E Electric", frequency: "MONTHLY", category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_GAS_AND_ELECTRICITY", avg_amount: 135, last_amount: 138.42, first_date: yearsAgo(2), last_date: monthDay(0, 15), is_active: 1, status: "MATURE", stream_type: "outflow" },
|
|
191
|
+
{ stream_id: "demo-rec-internet", account_id: CHK, merchant_name: "Comcast", description: "Comcast Internet", frequency: "MONTHLY", category: "RENT_AND_UTILITIES", subcategory: "RENT_AND_UTILITIES_INTERNET_AND_CABLE", avg_amount: 79.99, last_amount: 79.99, first_date: yearsAgo(2), last_date: monthDay(0, 12), is_active: 1, status: "MATURE", stream_type: "outflow" },
|
|
192
|
+
{ stream_id: "demo-rec-mortgage", account_id: CHK, merchant_name: "Chase", description: "Mortgage Payment", frequency: "MONTHLY", category: "LOAN_PAYMENTS", subcategory: "LOAN_PAYMENTS_MORTGAGE_PAYMENT", avg_amount: 2100, last_amount: 2100, first_date: yearsAgo(3), last_date: monthDay(0, 1), is_active: 1, status: "MATURE", stream_type: "outflow" },
|
|
193
|
+
{ stream_id: "demo-rec-gym", account_id: CHK, merchant_name: "Equinox", description: "Equinox Membership", frequency: "MONTHLY", category: "PERSONAL_CARE", subcategory: "PERSONAL_CARE_GYMS_AND_FITNESS_CENTERS", avg_amount: 95, last_amount: 95, first_date: yearsAgo(1), last_date: monthDay(0, 5), is_active: 1, status: "MATURE", stream_type: "outflow" },
|
|
194
|
+
{ stream_id: "demo-rec-icloud", account_id: CHK, merchant_name: "Apple", description: "iCloud Storage", frequency: "MONTHLY", category: "GENERAL_SERVICES", subcategory: "GENERAL_SERVICES_OTHER_GENERAL_SERVICES", avg_amount: 2.99, last_amount: 2.99, first_date: yearsAgo(4), last_date: monthDay(0, 3), is_active: 1, status: "MATURE", stream_type: "outflow" },
|
|
195
|
+
];
|
|
196
|
+
// ─── Budgets ─── //
|
|
197
|
+
export const budgets = [
|
|
198
|
+
{ category: "FOOD_AND_DRINK", monthly_limit: 600, period: "monthly" },
|
|
199
|
+
{ category: "GENERAL_MERCHANDISE", monthly_limit: 400, period: "monthly" },
|
|
200
|
+
{ category: "ENTERTAINMENT", monthly_limit: 150, period: "monthly" },
|
|
201
|
+
{ category: "TRANSPORTATION", monthly_limit: 200, period: "monthly" },
|
|
202
|
+
];
|
|
203
|
+
// ─── Goals ─── //
|
|
204
|
+
export const goals = [
|
|
205
|
+
{ name: "Emergency Fund", target_amount: 15000, current_amount: 6200, target_date: monthsFromNow(8), status: "active" },
|
|
206
|
+
{ name: "Japan Vacation", target_amount: 5000, current_amount: 2800, target_date: monthsFromNow(5), status: "active" },
|
|
207
|
+
{ name: "New Car Down Payment", target_amount: 8000, current_amount: 1200, target_date: monthsFromNow(14), status: "active" },
|
|
208
|
+
];
|
|
209
|
+
// ─── Daily Scores (14 days, building streaks) ─── //
|
|
210
|
+
export const dailyScores = [
|
|
211
|
+
{ date: daysAgo(13), score: 72, restaurant_count: 1, shopping_count: 0, food_spend: 55.20, total_spend: 180.40, zero_spend: 0, no_restaurant_streak: 0, no_shopping_streak: 1, on_pace_streak: 1 },
|
|
212
|
+
{ date: daysAgo(12), score: 68, restaurant_count: 0, shopping_count: 1, food_spend: 12.30, total_spend: 210.50, zero_spend: 0, no_restaurant_streak: 1, no_shopping_streak: 0, on_pace_streak: 0 },
|
|
213
|
+
{ date: daysAgo(11), score: 75, restaurant_count: 0, shopping_count: 0, food_spend: 0, total_spend: 95.00, zero_spend: 0, no_restaurant_streak: 2, no_shopping_streak: 1, on_pace_streak: 1 },
|
|
214
|
+
{ date: daysAgo(10), score: 92, restaurant_count: 0, shopping_count: 0, food_spend: 0, total_spend: 0, zero_spend: 1, no_restaurant_streak: 3, no_shopping_streak: 2, on_pace_streak: 2 },
|
|
215
|
+
{ date: daysAgo(9), score: 78, restaurant_count: 1, shopping_count: 0, food_spend: 42.80, total_spend: 120.30, zero_spend: 0, no_restaurant_streak: 0, no_shopping_streak: 3, on_pace_streak: 3 },
|
|
216
|
+
{ date: daysAgo(8), score: 65, restaurant_count: 0, shopping_count: 1, food_spend: 6.50, total_spend: 250.00, zero_spend: 0, no_restaurant_streak: 1, no_shopping_streak: 0, on_pace_streak: 0 },
|
|
217
|
+
{ date: daysAgo(7), score: 80, restaurant_count: 0, shopping_count: 0, food_spend: 34.67, total_spend: 34.67, zero_spend: 0, no_restaurant_streak: 2, no_shopping_streak: 1, on_pace_streak: 1 },
|
|
218
|
+
{ date: daysAgo(6), score: 85, restaurant_count: 0, shopping_count: 0, food_spend: 0, total_spend: 15.99, zero_spend: 0, no_restaurant_streak: 3, no_shopping_streak: 2, on_pace_streak: 2 },
|
|
219
|
+
{ date: daysAgo(5), score: 70, restaurant_count: 0, shopping_count: 0, food_spend: 0, total_spend: 49.99, zero_spend: 0, no_restaurant_streak: 4, no_shopping_streak: 3, on_pace_streak: 3 },
|
|
220
|
+
{ date: daysAgo(4), score: 82, restaurant_count: 0, shopping_count: 0, food_spend: 29.99, total_spend: 29.99, zero_spend: 0, no_restaurant_streak: 5, no_shopping_streak: 4, on_pace_streak: 4 },
|
|
221
|
+
{ date: daysAgo(3), score: 74, restaurant_count: 1, shopping_count: 0, food_spend: 58.35, total_spend: 58.35, zero_spend: 0, no_restaurant_streak: 0, no_shopping_streak: 5, on_pace_streak: 5 },
|
|
222
|
+
{ date: daysAgo(2), score: 83, restaurant_count: 0, shopping_count: 0, food_spend: 22.47, total_spend: 22.47, zero_spend: 0, no_restaurant_streak: 1, no_shopping_streak: 6, on_pace_streak: 6 },
|
|
223
|
+
{ date: yesterday(), score: 88, restaurant_count: 0, shopping_count: 0, food_spend: 6.50, total_spend: 30.80, zero_spend: 0, no_restaurant_streak: 2, no_shopping_streak: 7, on_pace_streak: 7 },
|
|
224
|
+
{ date: today(), score: 76, restaurant_count: 0, shopping_count: 0, food_spend: 5.75, total_spend: 855.74, zero_spend: 0, no_restaurant_streak: 3, no_shopping_streak: 8, on_pace_streak: 0 },
|
|
225
|
+
];
|
|
226
|
+
// ─── Achievements ─── //
|
|
227
|
+
export const achievements = [
|
|
228
|
+
{ key: "on_pace_7", name: "Clean Week", description: "7 consecutive days with all budgets on pace", unlocked_at: yesterday() },
|
|
229
|
+
{ key: "no_shopping_7", name: "Window Shopper", description: "7 days with zero shopping purchases", unlocked_at: daysAgo(2) },
|
|
230
|
+
{ key: "no_restaurant_7", name: "Kitchen Hero", description: "7-day no-restaurant streak", unlocked_at: daysAgo(5) },
|
|
231
|
+
{ key: "zero_hero", name: "Zero Hero", description: "A zero-spend day", unlocked_at: daysAgo(10) },
|
|
232
|
+
];
|
|
233
|
+
// ─── Net Worth History (30 days) ─── //
|
|
234
|
+
export const netWorthHistory = [];
|
|
235
|
+
{
|
|
236
|
+
const baseAssets = 539000;
|
|
237
|
+
const baseLiabilities = 287200;
|
|
238
|
+
for (let i = 29; i >= 0; i--) {
|
|
239
|
+
const jitter = Math.sin(i * 0.7) * 150 + (29 - i) * 130;
|
|
240
|
+
const assets = Math.round((baseAssets + jitter) * 100) / 100;
|
|
241
|
+
const liabJitter = Math.sin(i * 0.5) * 50 - (29 - i) * 8;
|
|
242
|
+
const liab = Math.round((baseLiabilities + liabJitter) * 100) / 100;
|
|
243
|
+
netWorthHistory.push({
|
|
244
|
+
date: daysAgo(i),
|
|
245
|
+
total_assets: assets,
|
|
246
|
+
total_liabilities: liab,
|
|
247
|
+
net_worth: Math.round((assets - liab) * 100) / 100,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// ─── Investment Transactions ─── //
|
|
252
|
+
export const investmentTransactions = [
|
|
253
|
+
{ investment_transaction_id: "demo-inv-tx-1", account_id: "demo-robinhood-brokerage", security_id: "demo-sec-aapl", date: daysAgo(30), name: "Buy AAPL", quantity: 5, amount: -1100.75, price: 220.15, fees: 0, type: "buy", subtype: "buy", iso_currency_code: "USD" },
|
|
254
|
+
{ investment_transaction_id: "demo-inv-tx-2", account_id: "demo-robinhood-brokerage", security_id: "demo-sec-voo", date: daysAgo(45), name: "Buy VOO", quantity: 10, amount: -5284.00, price: 528.40, fees: 0, type: "buy", subtype: "buy", iso_currency_code: "USD" },
|
|
255
|
+
{ investment_transaction_id: "demo-inv-tx-3", account_id: "demo-robinhood-brokerage", security_id: "demo-sec-voo", date: daysAgo(15), name: "Dividend VOO", quantity: 0, amount: -32.50, price: 0, fees: 0, type: "cash", subtype: "dividend", iso_currency_code: "USD" },
|
|
256
|
+
{ investment_transaction_id: "demo-inv-tx-4", account_id: "demo-schwab-401k", security_id: "demo-sec-vtsax", date: daysAgo(14), name: "401k Contribution", quantity: 8.5, amount: -1006.83, price: 118.45, fees: 0, type: "buy", subtype: "contribution", iso_currency_code: "USD" },
|
|
257
|
+
{ investment_transaction_id: "demo-inv-tx-5", account_id: "demo-schwab-401k", security_id: "demo-sec-vtsax", date: daysAgo(28), name: "401k Contribution", quantity: 8.5, amount: -1006.83, price: 118.45, fees: 0, type: "buy", subtype: "contribution", iso_currency_code: "USD" },
|
|
258
|
+
{ investment_transaction_id: "demo-inv-tx-6", account_id: "demo-schwab-401k", security_id: "demo-sec-vbtlx", date: daysAgo(20), name: "Dividend VBTLX", quantity: 0, amount: -18.40, price: 0, fees: 0, type: "cash", subtype: "dividend", iso_currency_code: "USD" },
|
|
259
|
+
];
|
|
260
|
+
// ─── Recurring Bills ─── //
|
|
261
|
+
export const recurringBills = [
|
|
262
|
+
{ name: "Rent", amount: 1850, day_of_month: 1, type: "housing", account_id: CHK },
|
|
263
|
+
{ name: "Mortgage", amount: 2100, day_of_month: 1, type: "housing", account_id: CHK },
|
|
264
|
+
{ name: "Electric", amount: 135, day_of_month: 15, type: "utility", account_id: CHK },
|
|
265
|
+
{ name: "Internet", amount: 79.99, day_of_month: 12, type: "utility", account_id: CHK },
|
|
266
|
+
{ name: "Car Insurance", amount: 145, day_of_month: 20, type: "insurance", account_id: CHK },
|
|
267
|
+
];
|
|
268
|
+
// ─── Memories ─── //
|
|
269
|
+
export const memories = [
|
|
270
|
+
{ content: "User prefers index fund investing and dollar-cost averaging", category: "preference" },
|
|
271
|
+
{ content: "User is saving for a trip to Japan next year", category: "goal" },
|
|
272
|
+
];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { deflateSync } from "zlib";
|
|
2
|
+
/** Generate a minimal 4x4 solid-color PNG as base64 from a hex color string. */
|
|
3
|
+
export function colorPng(hex) {
|
|
4
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
5
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
6
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
7
|
+
// PNG signature
|
|
8
|
+
const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
9
|
+
// IHDR chunk: 4x4, 8-bit RGB
|
|
10
|
+
const ihdrData = Buffer.alloc(13);
|
|
11
|
+
ihdrData.writeUInt32BE(4, 0); // width
|
|
12
|
+
ihdrData.writeUInt32BE(4, 4); // height
|
|
13
|
+
ihdrData[8] = 8; // bit depth
|
|
14
|
+
ihdrData[9] = 2; // color type: RGB
|
|
15
|
+
const ihdr = makeChunk("IHDR", ihdrData);
|
|
16
|
+
// IDAT chunk: raw pixel data (filter byte 0 + 4 RGB pixels per row)
|
|
17
|
+
const rowLen = 1 + 4 * 3; // filter byte + 4 pixels * 3 channels
|
|
18
|
+
const raw = Buffer.alloc(rowLen * 4);
|
|
19
|
+
for (let y = 0; y < 4; y++) {
|
|
20
|
+
const offset = y * rowLen;
|
|
21
|
+
raw[offset] = 0; // no filter
|
|
22
|
+
for (let x = 0; x < 4; x++) {
|
|
23
|
+
raw[offset + 1 + x * 3] = r;
|
|
24
|
+
raw[offset + 2 + x * 3] = g;
|
|
25
|
+
raw[offset + 3 + x * 3] = b;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const compressed = deflateSync(raw);
|
|
29
|
+
const idat = makeChunk("IDAT", compressed);
|
|
30
|
+
// IEND chunk
|
|
31
|
+
const iend = makeChunk("IEND", Buffer.alloc(0));
|
|
32
|
+
return Buffer.concat([signature, ihdr, idat, iend]).toString("base64");
|
|
33
|
+
}
|
|
34
|
+
function makeChunk(type, data) {
|
|
35
|
+
const len = Buffer.alloc(4);
|
|
36
|
+
len.writeUInt32BE(data.length);
|
|
37
|
+
const typeBuffer = Buffer.from(type, "ascii");
|
|
38
|
+
const crcInput = Buffer.concat([typeBuffer, data]);
|
|
39
|
+
const crc = Buffer.alloc(4);
|
|
40
|
+
crc.writeUInt32BE(crc32(crcInput) >>> 0);
|
|
41
|
+
return Buffer.concat([len, typeBuffer, data, crc]);
|
|
42
|
+
}
|
|
43
|
+
function crc32(buf) {
|
|
44
|
+
let c = 0xffffffff;
|
|
45
|
+
for (let i = 0; i < buf.length; i++) {
|
|
46
|
+
c ^= buf[i];
|
|
47
|
+
for (let j = 0; j < 8; j++) {
|
|
48
|
+
c = (c >>> 1) ^ (c & 1 ? 0xedb88320 : 0);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return c ^ 0xffffffff;
|
|
52
|
+
}
|
|
53
|
+
export const LOGOS = {
|
|
54
|
+
chase: colorPng("#003087"),
|
|
55
|
+
robinhood: colorPng("#00C805"),
|
|
56
|
+
schwab: colorPng("#00A0DF"),
|
|
57
|
+
amex: colorPng("#006FCF"),
|
|
58
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function seedDemoDb(dbPath: string, encryptionKey?: string): void;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import Database from "libsql";
|
|
2
|
+
import { resolve, dirname } from "path";
|
|
3
|
+
import { mkdirSync, existsSync, unlinkSync } from "fs";
|
|
4
|
+
import { migrate } from "../db/schema.js";
|
|
5
|
+
import { institutions, accounts, transactions, securities, holdings, liabilities, recurring, budgets, goals, dailyScores, achievements, netWorthHistory, investmentTransactions, recurringBills, memories, } from "./data.js";
|
|
6
|
+
export function seedDemoDb(dbPath, encryptionKey) {
|
|
7
|
+
const resolved = resolve(dbPath);
|
|
8
|
+
const dir = dirname(resolved);
|
|
9
|
+
mkdirSync(dir, { recursive: true });
|
|
10
|
+
// Remove existing demo DB for a clean slate
|
|
11
|
+
for (const suffix of ["", "-wal", "-shm"]) {
|
|
12
|
+
const f = resolved + suffix;
|
|
13
|
+
if (existsSync(f))
|
|
14
|
+
unlinkSync(f);
|
|
15
|
+
}
|
|
16
|
+
const opts = {};
|
|
17
|
+
if (encryptionKey) {
|
|
18
|
+
opts.encryptionCipher = "aes256cbc";
|
|
19
|
+
opts.encryptionKey = encryptionKey;
|
|
20
|
+
}
|
|
21
|
+
const db = new Database(dbPath, opts);
|
|
22
|
+
db.pragma("journal_mode = WAL");
|
|
23
|
+
db.pragma("foreign_keys = ON");
|
|
24
|
+
migrate(db);
|
|
25
|
+
const seed = db.transaction(() => {
|
|
26
|
+
// Institutions
|
|
27
|
+
const instStmt = db.prepare(`INSERT INTO institutions (item_id, access_token, name, products, logo, primary_color) VALUES (?, ?, ?, ?, ?, ?)`);
|
|
28
|
+
for (const i of institutions) {
|
|
29
|
+
instStmt.run(i.item_id, i.access_token, i.name, i.products, i.logo, i.primary_color);
|
|
30
|
+
}
|
|
31
|
+
// Accounts
|
|
32
|
+
const acctStmt = db.prepare(`INSERT INTO accounts (account_id, item_id, name, official_name, type, subtype, mask, current_balance, available_balance, currency, balance_limit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
33
|
+
for (const a of accounts) {
|
|
34
|
+
acctStmt.run(a.account_id, a.item_id, a.name, a.official_name, a.type, a.subtype, a.mask, a.current_balance, a.available_balance, a.currency, a.balance_limit);
|
|
35
|
+
}
|
|
36
|
+
// Transactions
|
|
37
|
+
const txStmt = db.prepare(`INSERT INTO transactions (transaction_id, account_id, amount, date, name, merchant_name, category, subcategory, pending, payment_channel) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
38
|
+
for (const t of transactions) {
|
|
39
|
+
txStmt.run(t.transaction_id, t.account_id, t.amount, t.date, t.name, t.merchant_name, t.category, t.subcategory, t.pending, t.payment_channel);
|
|
40
|
+
}
|
|
41
|
+
// Securities
|
|
42
|
+
const secStmt = db.prepare(`INSERT INTO securities (security_id, name, ticker, type, close_price, close_price_as_of) VALUES (?, ?, ?, ?, ?, ?)`);
|
|
43
|
+
for (const s of securities) {
|
|
44
|
+
secStmt.run(s.security_id, s.name, s.ticker, s.type, s.close_price, s.close_price_as_of);
|
|
45
|
+
}
|
|
46
|
+
// Holdings
|
|
47
|
+
const holdStmt = db.prepare(`INSERT INTO holdings (account_id, security_id, quantity, value, cost_basis, price, price_as_of, vested_value, vested_quantity) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
48
|
+
for (const h of holdings) {
|
|
49
|
+
holdStmt.run(h.account_id, h.security_id, h.quantity, h.value, h.cost_basis, h.price, h.price_as_of, h.vested_value, h.vested_quantity);
|
|
50
|
+
}
|
|
51
|
+
// Liabilities
|
|
52
|
+
const liabStmt = db.prepare(`INSERT INTO liabilities (account_id, type, interest_rate, origination_date, original_balance, current_balance, minimum_payment, next_payment_due, last_payment_amount, last_payment_date, credit_limit, last_statement_issue_date, is_overdue, apr_type, maturity_date, loan_type, property_address, escrow_balance, loan_status, loan_name, repayment_plan, expected_payoff_date, ytd_interest_paid, ytd_principal_paid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
53
|
+
for (const l of liabilities) {
|
|
54
|
+
liabStmt.run(l.account_id, l.type, l.interest_rate, l.origination_date, l.original_balance, l.current_balance, l.minimum_payment, l.next_payment_due, l.last_payment_amount, l.last_payment_date, l.credit_limit, l.last_statement_issue_date, l.is_overdue, l.apr_type, l.maturity_date, l.loan_type, l.property_address, l.escrow_balance, l.loan_status, l.loan_name, l.repayment_plan, l.expected_payoff_date, l.ytd_interest_paid, l.ytd_principal_paid);
|
|
55
|
+
}
|
|
56
|
+
// Recurring
|
|
57
|
+
const recStmt = db.prepare(`INSERT INTO recurring (stream_id, account_id, merchant_name, description, frequency, category, subcategory, avg_amount, last_amount, first_date, last_date, is_active, status, stream_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
58
|
+
for (const r of recurring) {
|
|
59
|
+
recStmt.run(r.stream_id, r.account_id, r.merchant_name, r.description, r.frequency, r.category, r.subcategory, r.avg_amount, r.last_amount, r.first_date, r.last_date, r.is_active, r.status, r.stream_type);
|
|
60
|
+
}
|
|
61
|
+
// Budgets
|
|
62
|
+
const budgetStmt = db.prepare(`INSERT INTO budgets (category, monthly_limit, period) VALUES (?, ?, ?)`);
|
|
63
|
+
for (const b of budgets) {
|
|
64
|
+
budgetStmt.run(b.category, b.monthly_limit, b.period);
|
|
65
|
+
}
|
|
66
|
+
// Goals
|
|
67
|
+
const goalStmt = db.prepare(`INSERT INTO goals (name, target_amount, current_amount, target_date, status) VALUES (?, ?, ?, ?, ?)`);
|
|
68
|
+
for (const g of goals) {
|
|
69
|
+
goalStmt.run(g.name, g.target_amount, g.current_amount, g.target_date, g.status);
|
|
70
|
+
}
|
|
71
|
+
// Daily Scores
|
|
72
|
+
const scoreStmt = db.prepare(`INSERT INTO daily_scores (date, score, restaurant_count, shopping_count, food_spend, total_spend, zero_spend, no_restaurant_streak, no_shopping_streak, on_pace_streak) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
73
|
+
for (const s of dailyScores) {
|
|
74
|
+
scoreStmt.run(s.date, s.score, s.restaurant_count, s.shopping_count, s.food_spend, s.total_spend, s.zero_spend, s.no_restaurant_streak, s.no_shopping_streak, s.on_pace_streak);
|
|
75
|
+
}
|
|
76
|
+
// Achievements
|
|
77
|
+
const achStmt = db.prepare(`INSERT INTO achievements (key, name, description, unlocked_at) VALUES (?, ?, ?, ?)`);
|
|
78
|
+
for (const a of achievements) {
|
|
79
|
+
achStmt.run(a.key, a.name, a.description, a.unlocked_at);
|
|
80
|
+
}
|
|
81
|
+
// Net Worth History
|
|
82
|
+
const nwStmt = db.prepare(`INSERT INTO net_worth_history (date, total_assets, total_liabilities, net_worth) VALUES (?, ?, ?, ?)`);
|
|
83
|
+
for (const nw of netWorthHistory) {
|
|
84
|
+
nwStmt.run(nw.date, nw.total_assets, nw.total_liabilities, nw.net_worth);
|
|
85
|
+
}
|
|
86
|
+
// Investment Transactions
|
|
87
|
+
const invTxStmt = db.prepare(`INSERT INTO investment_transactions (investment_transaction_id, account_id, security_id, date, name, quantity, amount, price, fees, type, subtype, iso_currency_code) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
88
|
+
for (const it of investmentTransactions) {
|
|
89
|
+
invTxStmt.run(it.investment_transaction_id, it.account_id, it.security_id, it.date, it.name, it.quantity, it.amount, it.price, it.fees, it.type, it.subtype, it.iso_currency_code);
|
|
90
|
+
}
|
|
91
|
+
// Recurring Bills
|
|
92
|
+
const billStmt = db.prepare(`INSERT INTO recurring_bills (name, amount, day_of_month, type, account_id) VALUES (?, ?, ?, ?, ?)`);
|
|
93
|
+
for (const b of recurringBills) {
|
|
94
|
+
billStmt.run(b.name, b.amount, b.day_of_month, b.type, b.account_id);
|
|
95
|
+
}
|
|
96
|
+
// Memories
|
|
97
|
+
const memStmt = db.prepare(`INSERT INTO memories (content, category) VALUES (?, ?)`);
|
|
98
|
+
for (const m of memories) {
|
|
99
|
+
memStmt.run(m.content, m.category);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
seed();
|
|
103
|
+
console.log("Demo database seeded successfully!\n");
|
|
104
|
+
console.log(` Institutions: ${institutions.length}`);
|
|
105
|
+
console.log(` Accounts: ${accounts.length}`);
|
|
106
|
+
console.log(` Transactions: ${transactions.length}`);
|
|
107
|
+
console.log(` Securities: ${securities.length}`);
|
|
108
|
+
console.log(` Holdings: ${holdings.length}`);
|
|
109
|
+
console.log(` Liabilities: ${liabilities.length}`);
|
|
110
|
+
console.log(` Recurring: ${recurring.length}`);
|
|
111
|
+
console.log(` Budgets: ${budgets.length}`);
|
|
112
|
+
console.log(` Goals: ${goals.length}`);
|
|
113
|
+
console.log(` Daily Scores: ${dailyScores.length}`);
|
|
114
|
+
console.log(` Achievements: ${achievements.length}`);
|
|
115
|
+
console.log(` Net Worth Days: ${netWorthHistory.length}`);
|
|
116
|
+
console.log(` Invest. Txns: ${investmentTransactions.length}`);
|
|
117
|
+
console.log(` Recurring Bills: ${recurringBills.length}`);
|
|
118
|
+
console.log(` Memories: ${memories.length}`);
|
|
119
|
+
console.log(`\n Database: ${resolve(dbPath)}`);
|
|
120
|
+
console.log(`\n Try it out:`);
|
|
121
|
+
console.log(` ray --demo status`);
|
|
122
|
+
console.log(` ray --demo accounts`);
|
|
123
|
+
console.log(` ray --demo spending`);
|
|
124
|
+
db.close();
|
|
125
|
+
}
|