neuronix-node 0.7.1 → 0.8.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.
@@ -8,6 +8,11 @@ async function handleBankReconciliation(task) {
8
8
  const bookTxns = input.book_transactions || [];
9
9
  const bankBal = input.bank_balance || 0;
10
10
  const bookBal = input.book_balance || 0;
11
+ // If we only have bank transactions and no book transactions,
12
+ // generate a transaction summary instead of a reconciliation
13
+ if (bankTxns.length > 0 && bookTxns.length === 0) {
14
+ return generateBankSummary(bankTxns, start);
15
+ }
11
16
  // Match transactions by amount and approximate date
12
17
  const matched = [];
13
18
  const unmatchedBank = [];
@@ -91,6 +96,86 @@ async function handleBankReconciliation(task) {
91
96
  duration_ms: Date.now() - start,
92
97
  };
93
98
  }
99
+ /**
100
+ * When only a bank statement is provided (no book data), generate a useful
101
+ * transaction summary instead of a broken reconciliation.
102
+ */
103
+ function generateBankSummary(txns, start) {
104
+ const deposits = txns.filter(t => t.type === "credit");
105
+ const withdrawals = txns.filter(t => t.type === "debit");
106
+ const totalDeposits = round(deposits.reduce((s, t) => s + t.amount, 0));
107
+ const totalWithdrawals = round(withdrawals.reduce((s, t) => s + t.amount, 0));
108
+ const netCashFlow = round(totalDeposits - totalWithdrawals);
109
+ // Categorize by description patterns
110
+ const categories = {};
111
+ for (const t of txns) {
112
+ const cat = categorizeTransaction(t.description);
113
+ if (!categories[cat])
114
+ categories[cat] = { total: 0, count: 0, type: t.type };
115
+ categories[cat].total += t.amount;
116
+ categories[cat].count += 1;
117
+ }
118
+ const sortedCats = Object.entries(categories).sort((a, b) => b[1].total - a[1].total);
119
+ const csv = [
120
+ "BANK STATEMENT SUMMARY",
121
+ `Date: ${new Date().toISOString().split("T")[0]}`,
122
+ `Transactions: ${txns.length}`,
123
+ "",
124
+ "CASH FLOW SUMMARY",
125
+ `Total Deposits (Credits),${fmt(totalDeposits)},${deposits.length} transactions`,
126
+ `Total Withdrawals (Debits),${fmt(totalWithdrawals)},${withdrawals.length} transactions`,
127
+ `Net Cash Flow,${fmt(netCashFlow)},${netCashFlow >= 0 ? "Positive" : "Negative"}`,
128
+ "",
129
+ "BY CATEGORY",
130
+ "Category,Total,Transactions,Type",
131
+ ...sortedCats.map(([cat, data]) => `${esc(cat)},${fmt(data.total)},${data.count},${data.type}`),
132
+ "",
133
+ "DEPOSITS",
134
+ "Date,Description,Amount,Reference",
135
+ ...deposits.sort((a, b) => b.amount - a.amount).map(t => `${t.date},${esc(t.description)},${fmt(t.amount)},${t.reference || ""}`),
136
+ "",
137
+ "WITHDRAWALS",
138
+ "Date,Description,Amount,Reference",
139
+ ...withdrawals.sort((a, b) => b.amount - a.amount).map(t => `${t.date},${esc(t.description)},${fmt(t.amount)},${t.reference || ""}`),
140
+ "",
141
+ "NOTE",
142
+ "This is a bank statement summary. To run a full reconciliation upload both your bank statement AND your book/ledger file.",
143
+ ];
144
+ return {
145
+ text: `Bank Statement Summary: ${txns.length} transactions | Deposits: ${fmt(totalDeposits)} | Withdrawals: ${fmt(totalWithdrawals)} | Net: ${fmt(netCashFlow)}`,
146
+ output_csv: csv.join("\n"),
147
+ summary: { total_deposits: totalDeposits, total_withdrawals: totalWithdrawals, net_cash_flow: netCashFlow, transaction_count: txns.length },
148
+ duration_ms: Date.now() - start,
149
+ };
150
+ }
151
+ function categorizeTransaction(desc) {
152
+ const lower = desc.toLowerCase();
153
+ if (/payroll|adp|gusto/.test(lower))
154
+ return "Payroll";
155
+ if (/deposit|payment|wire|transfer/i.test(lower) && /customer|inv-|client/.test(lower))
156
+ return "Customer Payments";
157
+ if (/rent|landlord/.test(lower))
158
+ return "Rent";
159
+ if (/insurance/.test(lower))
160
+ return "Insurance";
161
+ if (/aws|hosting|cloud|github|slack|zoom|google|datadog|sentry/.test(lower))
162
+ return "Software & Cloud";
163
+ if (/electric|pge|water|comcast|internet|phone/.test(lower))
164
+ return "Utilities";
165
+ if (/marketing|agency/.test(lower))
166
+ return "Marketing";
167
+ if (/interest/.test(lower))
168
+ return "Interest";
169
+ if (/fee|charge/.test(lower))
170
+ return "Bank Fees";
171
+ if (/uber|travel|lunch|dinner/.test(lower))
172
+ return "Travel & Entertainment";
173
+ if (/credit card|chase|amex/.test(lower))
174
+ return "Credit Card Payments";
175
+ if (/office|supplies|depot/.test(lower))
176
+ return "Office Supplies";
177
+ return "Other";
178
+ }
94
179
  function round(n) { return Math.round(n * 100) / 100; }
95
180
  function fmt(n) { return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }
96
181
  function esc(v) { return v.includes(",") ? `"${v}"` : v; }
@@ -303,15 +303,56 @@ function csvToPayroll(csv) {
303
303
  const rateCol = findCol(h, /rate|salary|wage|pay/);
304
304
  const hoursCol = findCol(h, /hours/);
305
305
  const deptCol = findCol(h, /department|dept/);
306
+ const healthInsCol = findCol(h, /health.?ins|medical|health/);
307
+ const k401Col = findCol(h, /401k|retirement/);
308
+ const otherDeductCol = findCol(h, /other.?deduct|deduction/);
306
309
  if (nameCol === -1 || rateCol === -1)
307
310
  return [];
308
311
  return csv.rows.map(r => {
309
312
  const type = typeCol >= 0 ? (r[typeCol]?.toLowerCase().includes("hourly") ? "hourly" : "salary") : "salary";
313
+ // Build deductions from columns
314
+ const deductions = [];
315
+ if (healthInsCol >= 0 && r[healthInsCol]) {
316
+ const amt = parseNum(r[healthInsCol]);
317
+ if (amt > 0)
318
+ deductions.push({ name: "Health Insurance", amount: amt });
319
+ }
320
+ if (k401Col >= 0 && r[k401Col]) {
321
+ const val = r[k401Col].trim();
322
+ // Could be a percentage like "6" or a dollar amount
323
+ if (val.includes("%") || (parseFloat(val) > 0 && parseFloat(val) <= 100 && !val.includes("$"))) {
324
+ // It's a percentage — will be calculated by the payroll handler based on gross
325
+ const pct = parseFloat(val);
326
+ const rate = parseNum(r[rateCol]);
327
+ const gross = type === "salary" ? rate / 26 : rate * (hoursCol >= 0 ? parseNum(r[hoursCol]) : 80);
328
+ const amt = Math.round(gross * (pct / 100) * 100) / 100;
329
+ if (amt > 0)
330
+ deductions.push({ name: `401k (${pct}%)`, amount: amt });
331
+ }
332
+ else {
333
+ const amt = parseNum(val);
334
+ if (amt > 0)
335
+ deductions.push({ name: "401k", amount: amt });
336
+ }
337
+ }
338
+ if (otherDeductCol >= 0 && r[otherDeductCol]) {
339
+ // Parse "HSA:150" or "HSA:150,Union:50" format
340
+ const parts = r[otherDeductCol].split(",");
341
+ for (const part of parts) {
342
+ const [name, amtStr] = part.split(":");
343
+ if (name && amtStr) {
344
+ const amt = parseFloat(amtStr.trim());
345
+ if (amt > 0)
346
+ deductions.push({ name: name.trim(), amount: amt });
347
+ }
348
+ }
349
+ }
310
350
  return {
311
351
  name: r[nameCol] || "Employee",
312
352
  type: type,
313
353
  rate: parseNum(r[rateCol]),
314
354
  hours: hoursCol >= 0 && r[hoursCol] ? parseNum(r[hoursCol]) : undefined,
355
+ deductions: deductions.length > 0 ? deductions : undefined,
315
356
  };
316
357
  }).filter(e => e.rate > 0);
317
358
  }
package/dist/index.js CHANGED
@@ -236,7 +236,11 @@ async function main() {
236
236
  const resources = await (0, resource_limiter_js_1.checkResources)();
237
237
  if (!resources.allowed) {
238
238
  log(`Task deferred: ${resources.reason}`);
239
- // Don't complete as failed let another node pick it up
239
+ // Put task back to pending so another node can pick it up
240
+ try {
241
+ await (0, api_js_1.completeTask)(config, task.id, "failed", { error: "Node resource limits exceeded, retrying on another node" }, 0);
242
+ }
243
+ catch { }
240
244
  continue;
241
245
  }
242
246
  // ── Security: Log task receipt ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuronix-node",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Neuronix GPU Provider Node — earn by contributing compute to the Neuronix network",
5
5
  "main": "dist/index.js",
6
6
  "bin": {