neuroalpha 0.0.0-stage → 0.2.0

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.
Files changed (46) hide show
  1. package/README.md +164 -3
  2. package/bin/cli.js +69 -0
  3. package/config/strategy.json +33 -0
  4. package/docs//320/230/320/235/320/241/320/242/320/240/320/243/320/232/320/246/320/230/320/257.md +156 -0
  5. package/package.json +43 -6
  6. package/public/app.js +2410 -0
  7. package/public/index.html +337 -0
  8. package/public/landing.html +870 -0
  9. package/public/legal.html +90 -0
  10. package/public/styles.css +343 -0
  11. package/server/api/routes.js +333 -0
  12. package/server/auth/index.js +340 -0
  13. package/server/billing/yookassa.js +132 -0
  14. package/server/brokers/index.js +645 -0
  15. package/server/config.js +44 -0
  16. package/server/data/moex.js +165 -0
  17. package/server/db.js +420 -0
  18. package/server/hub.js +14 -0
  19. package/server/index.js +92 -0
  20. package/server/llm/client.js +187 -0
  21. package/server/llm/providers.js +355 -0
  22. package/server/model/dataset.js +352 -0
  23. package/server/model/forecast.js +121 -0
  24. package/server/model/gpu-worker.html +11 -0
  25. package/server/model/gpu-worker.js +60 -0
  26. package/server/model/linalg.js +142 -0
  27. package/server/model/live.js +486 -0
  28. package/server/model/metrics.js +134 -0
  29. package/server/model/models.js +459 -0
  30. package/server/model/profiles.js +166 -0
  31. package/server/model/research.js +880 -0
  32. package/server/model/tf-node.js +26 -0
  33. package/server/model/transformer-core.js +330 -0
  34. package/server/model/transformer-gpu.js +221 -0
  35. package/server/model/transformer-pool.js +200 -0
  36. package/server/model/transformer-worker.js +28 -0
  37. package/server/model/transformer.js +57 -0
  38. package/server/model/vocab.js +28 -0
  39. package/server/news/index.js +309 -0
  40. package/server/notify/index.js +168 -0
  41. package/server/params-meta.js +63 -0
  42. package/server/settings.js +79 -0
  43. package/server/strategist/analytics.js +150 -0
  44. package/server/strategist/index.js +484 -0
  45. package/server/strategist/tools.js +302 -0
  46. package/server/util.js +9 -0
@@ -0,0 +1,132 @@
1
+ import crypto from 'node:crypto';
2
+ import { db, kvGet, kvSet } from '../db.js';
3
+ import { PLANS, encryptSecret, decryptSecret, getUserById, publicUser } from '../auth/index.js';
4
+
5
+ export function getBillingConfig() {
6
+ const saved = kvGet('yookassaConfig') || {};
7
+ const shopId = String(saved.shopId || process.env.YOOKASSA_SHOP_ID || '').trim();
8
+ const secretKey = decryptSecret(saved.secretKey || '') || String(process.env.YOOKASSA_SECRET_KEY || '').trim();
9
+ const sendReceipt = saved.sendReceipt !== undefined ? Boolean(saved.sendReceipt) : (process.env.YOOKASSA_RECEIPT === '1');
10
+ const vatCode = Number(saved.vatCode || process.env.YOOKASSA_VAT_CODE || 1);
11
+ return { shopId, secretKey, sendReceipt, vatCode, enabled: Boolean(shopId && secretKey) };
12
+ }
13
+
14
+ export function publicBillingConfig() {
15
+ const c = getBillingConfig();
16
+ return { shopId: c.shopId, hasSecret: Boolean(c.secretKey), sendReceipt: c.sendReceipt, vatCode: c.vatCode, enabled: c.enabled, plans: PLANS };
17
+ }
18
+
19
+ export function saveBillingConfig(input = {}) {
20
+ const prev = kvGet('yookassaConfig') || {};
21
+ const next = {
22
+ shopId: String(input.shopId ?? prev.shopId ?? '').trim(),
23
+ secretKey: input.clearSecret ? '' : (input.secretKey && String(input.secretKey).trim() ? encryptSecret(input.secretKey) : (prev.secretKey || '')),
24
+ sendReceipt: input.sendReceipt !== undefined ? Boolean(input.sendReceipt) : Boolean(prev.sendReceipt),
25
+ vatCode: Number(input.vatCode ?? prev.vatCode ?? 1) || 1,
26
+ };
27
+ kvSet('yookassaConfig', next);
28
+ return publicBillingConfig();
29
+ }
30
+
31
+ export async function createYookassaPayment({ user, plan, months = 1, returnOrigin = 'http://localhost:3000' }) {
32
+ if (!user) throw new Error('Войдите в аккаунт для оформления подписки');
33
+ const planObj = PLANS[plan];
34
+ if (!planObj || planObj.priceRub <= 0) throw new Error('Выберите платный тариф (Инвестор или Профи)');
35
+ const m = Math.max(1, Math.min(12, Math.floor(Number(months) || 1)));
36
+ const discount = m >= 12 ? 0.8 : m >= 3 ? 0.9 : 1;
37
+ const amountRub = Math.round(planObj.priceRub * m * discount);
38
+ const cfg = getBillingConfig();
39
+ if (!cfg.enabled) throw new Error('Платёжный шлюз ЮKassa ещё не настроен администратором (укажите Shop ID и секретный ключ)');
40
+
41
+ const description = `Подписка «${planObj.name}» на ${m} мес. (${user.email})`;
42
+ const payload = {
43
+ amount: { value: amountRub.toFixed(2), currency: 'RUB' },
44
+ capture: true,
45
+ confirmation: {
46
+ type: 'redirect',
47
+ return_url: `${returnOrigin.replace(/\/+$/, '')}/?payment_check=1`,
48
+ },
49
+ description,
50
+ metadata: { userId: String(user.id), plan, months: String(m) },
51
+ };
52
+ if (cfg.sendReceipt && user.email) {
53
+ payload.receipt = {
54
+ customer: { email: user.email },
55
+ items: [{
56
+ description: description.slice(0, 128),
57
+ quantity: '1.00',
58
+ amount: { value: amountRub.toFixed(2), currency: 'RUB' },
59
+ vat_code: cfg.vatCode,
60
+ payment_mode: 'full_payment',
61
+ payment_subject: 'service',
62
+ }],
63
+ };
64
+ }
65
+
66
+ const auth = Buffer.from(`${cfg.shopId}:${cfg.secretKey}`).toString('base64');
67
+ const r = await fetch('https://api.yookassa.ru/v3/payments', {
68
+ method: 'POST',
69
+ headers: {
70
+ Authorization: `Basic ${auth}`,
71
+ 'Idempotence-Key': crypto.randomUUID(),
72
+ 'Content-Type': 'application/json',
73
+ },
74
+ body: JSON.stringify(payload),
75
+ signal: AbortSignal.timeout(20000),
76
+ });
77
+ const j = await r.json().catch(() => ({}));
78
+ if (!r.ok || !j.id) throw new Error(j.description || `Ошибка ЮKassa: HTTP ${r.status}`);
79
+
80
+ db.prepare(`INSERT OR REPLACE INTO payments (id, user_id, plan, months, amount_rub, status, created_at, raw)
81
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(j.id, user.id, plan, m, amountRub, j.status || 'pending', Date.now(), JSON.stringify(j));
82
+
83
+ return {
84
+ paymentId: j.id,
85
+ status: j.status,
86
+ amountRub,
87
+ confirmationUrl: j.confirmation?.confirmation_url || null,
88
+ };
89
+ }
90
+
91
+ function applyPaidSubscription(payRow, remoteObj) {
92
+ if (!payRow || payRow.status === 'succeeded') return;
93
+ const u = getUserById(payRow.user_id);
94
+ if (!u) return;
95
+ const baseTs = u.plan === payRow.plan && u.plan_until > Date.now() ? u.plan_until : Date.now();
96
+ const nextUntil = baseTs + (Number(payRow.months) || 1) * 30 * 86400000;
97
+ db.prepare('UPDATE users SET plan = ?, plan_until = ? WHERE id = ?').run(payRow.plan, nextUntil, u.id);
98
+ db.prepare('UPDATE payments SET status = ?, paid_at = ?, raw = ? WHERE id = ?')
99
+ .run('succeeded', Date.now(), JSON.stringify(remoteObj || {}), payRow.id);
100
+ }
101
+
102
+ export async function verifyYookassaPayment(paymentId) {
103
+ const row = db.prepare('SELECT * FROM payments WHERE id = ?').get(String(paymentId));
104
+ if (!row) throw new Error('Платёж не найден');
105
+ if (row.status === 'succeeded') return { status: 'succeeded', user: publicUser(getUserById(row.user_id)) };
106
+ const cfg = getBillingConfig();
107
+ if (!cfg.enabled) throw new Error('ЮKassa не настроена');
108
+ const auth = Buffer.from(`${cfg.shopId}:${cfg.secretKey}`).toString('base64');
109
+ const r = await fetch(`https://api.yookassa.ru/v3/payments/${encodeURIComponent(row.id)}`, {
110
+ headers: { Authorization: `Basic ${auth}` },
111
+ signal: AbortSignal.timeout(15000),
112
+ });
113
+ const j = await r.json().catch(() => ({}));
114
+ if (!r.ok) throw new Error(j.description || `HTTP ${r.status}`);
115
+ if (j.status === 'succeeded' && j.paid) {
116
+ applyPaidSubscription(row, j);
117
+ } else if (j.status && j.status !== row.status) {
118
+ db.prepare('UPDATE payments SET status = ?, raw = ? WHERE id = ?').run(j.status, JSON.stringify(j), row.id);
119
+ }
120
+ return { status: j.status, user: publicUser(getUserById(row.user_id)) };
121
+ }
122
+
123
+ export async function verifyUserLatestPayment(userId) {
124
+ const row = db.prepare("SELECT * FROM payments WHERE user_id = ? AND status = 'pending' ORDER BY created_at DESC LIMIT 1").get(Number(userId));
125
+ if (!row) return { status: 'none', user: publicUser(getUserById(userId)) };
126
+ return verifyYookassaPayment(row.id);
127
+ }
128
+
129
+ export function listAllPayments(limit = 100) {
130
+ return db.prepare(`SELECT p.id, p.user_id, u.email, p.plan, p.months, p.amount_rub, p.status, p.created_at, p.paid_at
131
+ FROM payments p LEFT JOIN users u ON u.id = p.user_id ORDER BY p.created_at DESC LIMIT ?`).all(limit);
132
+ }