neuronix-node 0.2.0 → 0.5.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.
- package/dist/handlers/ap-aging.d.ts +2 -0
- package/dist/handlers/ap-aging.js +91 -0
- package/dist/handlers/ar-aging.d.ts +2 -0
- package/dist/handlers/ar-aging.js +101 -0
- package/dist/handlers/bank-reconciliation.d.ts +2 -0
- package/dist/handlers/bank-reconciliation.js +96 -0
- package/dist/handlers/budget-vs-actuals.d.ts +2 -0
- package/dist/handlers/budget-vs-actuals.js +70 -0
- package/dist/handlers/cash-flow.d.ts +2 -0
- package/dist/handlers/cash-flow.js +87 -0
- package/dist/handlers/chat.d.ts +5 -0
- package/dist/handlers/chat.js +336 -0
- package/dist/handlers/department-spending.d.ts +2 -0
- package/dist/handlers/department-spending.js +83 -0
- package/dist/handlers/depreciation.d.ts +2 -0
- package/dist/handlers/depreciation.js +93 -0
- package/dist/handlers/expense.js +166 -35
- package/dist/handlers/file-processor.js +1 -1
- package/dist/handlers/index.d.ts +0 -6
- package/dist/handlers/index.js +26 -6
- package/dist/handlers/payroll.d.ts +2 -0
- package/dist/handlers/payroll.js +101 -0
- package/dist/handlers/sales-tax.d.ts +2 -0
- package/dist/handlers/sales-tax.js +89 -0
- package/dist/handlers/variance-analysis.d.ts +2 -0
- package/dist/handlers/variance-analysis.js +73 -0
- package/dist/handlers/w2-1099.d.ts +2 -0
- package/dist/handlers/w2-1099.js +87 -0
- package/package.json +1 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleChat = handleChat;
|
|
4
|
+
const inference_js_1 = require("../inference.js");
|
|
5
|
+
// Import all action handlers
|
|
6
|
+
const chart_js_1 = require("./chart.js");
|
|
7
|
+
const invoice_js_1 = require("./invoice.js");
|
|
8
|
+
const expense_js_1 = require("./expense.js");
|
|
9
|
+
const pnl_js_1 = require("./pnl.js");
|
|
10
|
+
const ar_aging_js_1 = require("./ar-aging.js");
|
|
11
|
+
const ap_aging_js_1 = require("./ap-aging.js");
|
|
12
|
+
const bank_reconciliation_js_1 = require("./bank-reconciliation.js");
|
|
13
|
+
const budget_vs_actuals_js_1 = require("./budget-vs-actuals.js");
|
|
14
|
+
const payroll_js_1 = require("./payroll.js");
|
|
15
|
+
const sales_tax_js_1 = require("./sales-tax.js");
|
|
16
|
+
const depreciation_js_1 = require("./depreciation.js");
|
|
17
|
+
const cash_flow_js_1 = require("./cash-flow.js");
|
|
18
|
+
const department_spending_js_1 = require("./department-spending.js");
|
|
19
|
+
const variance_analysis_js_1 = require("./variance-analysis.js");
|
|
20
|
+
const w2_1099_js_1 = require("./w2-1099.js");
|
|
21
|
+
// Intent detection patterns — maps user language to handler actions
|
|
22
|
+
const INTENT_PATTERNS = [
|
|
23
|
+
// Expense report
|
|
24
|
+
{ pattern: /\b(expense report|expense summary|categorize expenses|expense breakdown|spending report)\b/i, action: "expense_report", handler: "expense_report", description: "generating an expense report" },
|
|
25
|
+
// P&L
|
|
26
|
+
{ pattern: /\b(p&l|profit.?(?:and|&)?.?loss|income statement|net income|profit margin)\b/i, action: "pnl", handler: "pnl", description: "generating a Profit & Loss statement" },
|
|
27
|
+
// Invoice
|
|
28
|
+
{ pattern: /\b(create invoice|generate invoice|make (?:an )?invoice|send invoice|bill (?:the )?client)\b/i, action: "invoice", handler: "invoice", description: "creating an invoice" },
|
|
29
|
+
// Chart
|
|
30
|
+
{ pattern: /\b(make (?:a |me )?chart|create (?:a )?(?:chart|graph|plot)|bar chart|pie chart|line chart|visualize|visualization)\b/i, action: "chart", handler: "chart", description: "creating a chart" },
|
|
31
|
+
// AR Aging
|
|
32
|
+
{ pattern: /\b(ar aging|accounts receivable|who owes|outstanding invoices|overdue invoices|collections|receivables aging)\b/i, action: "ar_aging", handler: "ar_aging", description: "generating an Accounts Receivable aging report" },
|
|
33
|
+
// AP Aging
|
|
34
|
+
{ pattern: /\b(ap aging|accounts payable|what (?:do )?we owe|outstanding bills|overdue bills|vendor payments|payables aging)\b/i, action: "ap_aging", handler: "ap_aging", description: "generating an Accounts Payable aging report" },
|
|
35
|
+
// Bank Reconciliation
|
|
36
|
+
{ pattern: /\b(bank rec|reconcil|match.?(?:bank|transactions)|bank statement|reconcile (?:the )?bank)\b/i, action: "bank_reconciliation", handler: "bank_reconciliation", description: "running a bank reconciliation" },
|
|
37
|
+
// Budget vs Actuals
|
|
38
|
+
{ pattern: /\b(budget vs|budget.?actual|over budget|under budget|budget comparison|budget review|spending vs budget)\b/i, action: "budget_vs_actuals", handler: "budget_vs_actuals", description: "comparing budget vs actuals" },
|
|
39
|
+
// Payroll
|
|
40
|
+
{ pattern: /\b(run payroll|payroll calc|process payroll|payroll register|employee pay|calculate payroll|pay (?:the )?employees)\b/i, action: "payroll", handler: "payroll", description: "running payroll calculations" },
|
|
41
|
+
// Sales Tax
|
|
42
|
+
{ pattern: /\b(sales tax|tax collected|tax filing|tax report|tax liability|state tax)\b/i, action: "sales_tax", handler: "sales_tax", description: "calculating sales tax" },
|
|
43
|
+
// Depreciation
|
|
44
|
+
{ pattern: /\b(depreciation|fixed asset|book value|asset schedule|depreciate)\b/i, action: "depreciation", handler: "depreciation", description: "calculating depreciation" },
|
|
45
|
+
// Cash Flow
|
|
46
|
+
{ pattern: /\b(cash flow|cash position|cash statement|operating cash|where.?(?:is|did) (?:the |our )?(?:cash|money))\b/i, action: "cash_flow", handler: "cash_flow", description: "generating a cash flow statement" },
|
|
47
|
+
// Department Spending
|
|
48
|
+
{ pattern: /\b(department spending|spending by department|departmental|team spending|which department|cost center)\b/i, action: "department_spending", handler: "department_spending", description: "generating a department spending report" },
|
|
49
|
+
// Variance Analysis
|
|
50
|
+
{ pattern: /\b(variance|significant.?difference|budget.?deviation|why.?(?:is|are|did).?(?:we|it).?(?:over|under|different))\b/i, action: "variance_analysis", handler: "variance_analysis", description: "running a variance analysis" },
|
|
51
|
+
// W-2/1099
|
|
52
|
+
{ pattern: /\b(w-?2|1099|year.?end.?tax|tax.?form|issue.?w2|file.?1099|annual.?tax)\b/i, action: "w2_1099", handler: "w2_1099", description: "compiling W-2 and 1099 data" },
|
|
53
|
+
];
|
|
54
|
+
// Map action names to handler functions
|
|
55
|
+
const ACTION_HANDLERS = {
|
|
56
|
+
expense_report: expense_js_1.handleExpenseReport,
|
|
57
|
+
pnl: pnl_js_1.handlePnl,
|
|
58
|
+
invoice: invoice_js_1.handleInvoice,
|
|
59
|
+
chart: chart_js_1.handleChart,
|
|
60
|
+
ar_aging: ar_aging_js_1.handleARAging,
|
|
61
|
+
ap_aging: ap_aging_js_1.handleAPAging,
|
|
62
|
+
bank_reconciliation: bank_reconciliation_js_1.handleBankReconciliation,
|
|
63
|
+
budget_vs_actuals: budget_vs_actuals_js_1.handleBudgetVsActuals,
|
|
64
|
+
payroll: payroll_js_1.handlePayroll,
|
|
65
|
+
sales_tax: sales_tax_js_1.handleSalesTax,
|
|
66
|
+
depreciation: depreciation_js_1.handleDepreciation,
|
|
67
|
+
cash_flow: cash_flow_js_1.handleCashFlow,
|
|
68
|
+
department_spending: department_spending_js_1.handleDepartmentSpending,
|
|
69
|
+
variance_analysis: variance_analysis_js_1.handleVarianceAnalysis,
|
|
70
|
+
w2_1099: w2_1099_js_1.handleW21099,
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Detect what the user wants to do from their message.
|
|
74
|
+
*/
|
|
75
|
+
function detectIntent(messages) {
|
|
76
|
+
// Check the last user message
|
|
77
|
+
const lastUser = [...messages].reverse().find(m => m.role === "user");
|
|
78
|
+
if (!lastUser)
|
|
79
|
+
return null;
|
|
80
|
+
const text = lastUser.content;
|
|
81
|
+
for (const intent of INTENT_PATTERNS) {
|
|
82
|
+
if (intent.pattern.test(text)) {
|
|
83
|
+
return { action: intent.action, handler: intent.handler, description: intent.description };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Chat handler — processes conversational messages AND triggers actions.
|
|
90
|
+
*/
|
|
91
|
+
async function handleChat(task) {
|
|
92
|
+
const start = Date.now();
|
|
93
|
+
const input = task.input_payload;
|
|
94
|
+
const messages = input.messages || [];
|
|
95
|
+
// Step 1: Detect if the user wants an action
|
|
96
|
+
const intent = detectIntent(messages);
|
|
97
|
+
if (intent) {
|
|
98
|
+
// User wants something done — run the handler
|
|
99
|
+
const handler = ACTION_HANDLERS[intent.action];
|
|
100
|
+
if (handler) {
|
|
101
|
+
try {
|
|
102
|
+
const actionResult = await handler({
|
|
103
|
+
type: intent.action,
|
|
104
|
+
input_payload: task.input_payload,
|
|
105
|
+
});
|
|
106
|
+
// Build a natural response that includes the action result
|
|
107
|
+
const responseText = buildActionResponse(intent, actionResult);
|
|
108
|
+
return {
|
|
109
|
+
text: responseText,
|
|
110
|
+
content: responseText,
|
|
111
|
+
action_taken: intent.action,
|
|
112
|
+
action_description: intent.description,
|
|
113
|
+
action_result: actionResult,
|
|
114
|
+
// Pass through output_csv so it gets saved as a file
|
|
115
|
+
output_csv: actionResult.output_csv,
|
|
116
|
+
image_base64: actionResult.image_base64,
|
|
117
|
+
invoice_html: actionResult.invoice_html,
|
|
118
|
+
conversation_id: input.conversation_id,
|
|
119
|
+
deployment_id: input.deployment_id,
|
|
120
|
+
duration_ms: Date.now() - start,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
const durationMs = Date.now() - start;
|
|
125
|
+
return {
|
|
126
|
+
text: `I tried ${intent.description} but ran into an error: ${err}. Could you check the data and try again?`,
|
|
127
|
+
content: `I tried ${intent.description} but ran into an error: ${err}. Could you check the data and try again?`,
|
|
128
|
+
action_taken: intent.action,
|
|
129
|
+
action_error: String(err),
|
|
130
|
+
conversation_id: input.conversation_id,
|
|
131
|
+
deployment_id: input.deployment_id,
|
|
132
|
+
duration_ms: durationMs,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Step 2: No action detected — regular conversation
|
|
138
|
+
// Try LLM for conversational response
|
|
139
|
+
const modelIds = ["llama3-8b", "mistral-7b", "phi-2", "tinyllama-1.1b"];
|
|
140
|
+
const loadedModel = modelIds.find((id) => (0, inference_js_1.isModelLoaded)(id));
|
|
141
|
+
if (!loadedModel) {
|
|
142
|
+
// No model loaded — provide a helpful static response
|
|
143
|
+
const helpText = generateHelpResponse(messages);
|
|
144
|
+
return {
|
|
145
|
+
text: helpText,
|
|
146
|
+
content: helpText,
|
|
147
|
+
conversation_id: input.conversation_id,
|
|
148
|
+
deployment_id: input.deployment_id,
|
|
149
|
+
duration_ms: Date.now() - start,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const prompt = formatConversation(messages);
|
|
154
|
+
const { text, durationMs } = await (0, inference_js_1.runInference)(loadedModel, prompt, input.max_tokens || 512);
|
|
155
|
+
const cleanedText = cleanResponse(text);
|
|
156
|
+
// Check for preference updates
|
|
157
|
+
const memoryUpdates = detectPreferences(messages);
|
|
158
|
+
let finalText = cleanedText;
|
|
159
|
+
if (memoryUpdates.length > 0) {
|
|
160
|
+
finalText += "\n" + memoryUpdates.map(m => `[MEMORY_UPDATE]${m.key}: ${m.value}[/MEMORY_UPDATE]`).join("\n");
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
text: finalText,
|
|
164
|
+
content: finalText,
|
|
165
|
+
conversation_id: input.conversation_id,
|
|
166
|
+
deployment_id: input.deployment_id,
|
|
167
|
+
model: loadedModel,
|
|
168
|
+
duration_ms: durationMs,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
const helpText = generateHelpResponse(messages);
|
|
173
|
+
return {
|
|
174
|
+
text: helpText,
|
|
175
|
+
content: helpText,
|
|
176
|
+
conversation_id: input.conversation_id,
|
|
177
|
+
deployment_id: input.deployment_id,
|
|
178
|
+
duration_ms: Date.now() - start,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Build a natural language response after running an action.
|
|
184
|
+
*/
|
|
185
|
+
function buildActionResponse(intent, result) {
|
|
186
|
+
const summary = result.summary;
|
|
187
|
+
const parts = [];
|
|
188
|
+
parts.push(`Done — I've finished ${intent.description}.`);
|
|
189
|
+
// Add key stats from the result
|
|
190
|
+
switch (intent.action) {
|
|
191
|
+
case "expense_report":
|
|
192
|
+
if (summary)
|
|
193
|
+
parts.push(`\nTotal expenses: $${summary.total?.toLocaleString() || "N/A"} across ${summary.expense_count || 0} items in ${summary.category_count || 0} categories.`);
|
|
194
|
+
break;
|
|
195
|
+
case "pnl":
|
|
196
|
+
if (summary)
|
|
197
|
+
parts.push(`\nRevenue: $${summary.revenue_total?.toLocaleString() || "0"} | Expenses: $${summary.expense_total?.toLocaleString() || "0"} | Net Income: $${summary.net_income?.toLocaleString() || "0"} (${summary.margin_pct || 0}% margin)`);
|
|
198
|
+
break;
|
|
199
|
+
case "invoice":
|
|
200
|
+
if (result.total)
|
|
201
|
+
parts.push(`\nInvoice ${result.invoice_number || ""} for $${result.total?.toLocaleString() || "0"}, due ${result.due_date || "in 30 days"}.`);
|
|
202
|
+
break;
|
|
203
|
+
case "ar_aging":
|
|
204
|
+
if (summary)
|
|
205
|
+
parts.push(`\nTotal outstanding: $${summary.total_outstanding?.toLocaleString() || "0"} across ${summary.customer_count || 0} customers.`);
|
|
206
|
+
break;
|
|
207
|
+
case "ap_aging":
|
|
208
|
+
if (summary)
|
|
209
|
+
parts.push(`\nTotal owed: $${summary.total_owed?.toLocaleString() || "0"} across ${summary.vendor_count || 0} vendors.`);
|
|
210
|
+
break;
|
|
211
|
+
case "bank_reconciliation":
|
|
212
|
+
if (summary)
|
|
213
|
+
parts.push(`\n${summary.reconciled ? "The accounts are reconciled." : `There's a difference of $${summary.difference?.toLocaleString() || "0"} that needs review.`} Matched ${summary.matched_count || 0} transactions.`);
|
|
214
|
+
break;
|
|
215
|
+
case "budget_vs_actuals":
|
|
216
|
+
if (summary)
|
|
217
|
+
parts.push(`\nBudget: $${summary.total_budget?.toLocaleString() || "0"} | Actual: $${summary.total_actual?.toLocaleString() || "0"} | Variance: ${summary.total_variance >= 0 ? "+" : ""}$${summary.total_variance?.toLocaleString() || "0"} (${summary.total_variance_pct || 0}%)`);
|
|
218
|
+
break;
|
|
219
|
+
case "payroll":
|
|
220
|
+
if (summary)
|
|
221
|
+
parts.push(`\n${summary.employee_count || 0} employees | Gross: $${summary.total_gross?.toLocaleString() || "0"} | Net: $${summary.total_net?.toLocaleString() || "0"} | Employer cost: $${summary.employer_cost?.toLocaleString() || "0"}`);
|
|
222
|
+
break;
|
|
223
|
+
case "sales_tax":
|
|
224
|
+
if (summary)
|
|
225
|
+
parts.push(`\nTax collected: $${summary.total_tax?.toLocaleString() || "0"} on $${summary.total_taxable?.toLocaleString() || "0"} taxable sales across ${summary.jurisdictions || 0} jurisdictions.`);
|
|
226
|
+
break;
|
|
227
|
+
case "depreciation":
|
|
228
|
+
if (summary)
|
|
229
|
+
parts.push(`\n${summary.asset_count || 0} assets | Book value: $${summary.total_book_value?.toLocaleString() || "0"} | This year's depreciation: $${summary.current_year?.toLocaleString() || "0"}`);
|
|
230
|
+
break;
|
|
231
|
+
case "cash_flow":
|
|
232
|
+
if (summary)
|
|
233
|
+
parts.push(`\nOperating: $${summary.net_operating?.toLocaleString() || "0"} | Investing: $${summary.net_investing?.toLocaleString() || "0"} | Financing: $${summary.net_financing?.toLocaleString() || "0"} | Ending balance: $${summary.ending_balance?.toLocaleString() || "0"}`);
|
|
234
|
+
break;
|
|
235
|
+
case "department_spending":
|
|
236
|
+
if (summary)
|
|
237
|
+
parts.push(`\n${summary.department_count || 0} departments | Total: $${summary.grand_total?.toLocaleString() || "0"}`);
|
|
238
|
+
break;
|
|
239
|
+
case "variance_analysis":
|
|
240
|
+
if (summary)
|
|
241
|
+
parts.push(`\n${summary.significant_count || 0} significant variances found. ${summary.favorable_count || 0} favorable, ${summary.unfavorable_count || 0} unfavorable.`);
|
|
242
|
+
break;
|
|
243
|
+
case "w2_1099":
|
|
244
|
+
if (summary)
|
|
245
|
+
parts.push(`\n${summary.w2_count || 0} W-2s and ${summary.contractor_count || 0} 1099s to issue. Filing deadline: ${summary.filing_deadline || "January 31"}.`);
|
|
246
|
+
break;
|
|
247
|
+
case "chart":
|
|
248
|
+
parts.push(`\nThe chart has been generated.`);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if (result.output_csv) {
|
|
252
|
+
parts.push("\nThe report has been saved to your outputs folder. You can download it from the Files tab.");
|
|
253
|
+
}
|
|
254
|
+
parts.push("\nAnything else you need?");
|
|
255
|
+
return parts.join("");
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Generate a helpful response when no LLM is available.
|
|
259
|
+
*/
|
|
260
|
+
function generateHelpResponse(messages) {
|
|
261
|
+
const lastUser = [...messages].reverse().find(m => m.role === "user");
|
|
262
|
+
const userText = lastUser?.content.toLowerCase() || "";
|
|
263
|
+
if (/help|what can you do|capabilities/.test(userText)) {
|
|
264
|
+
return `Here's what I can do for you. Just ask in plain English:
|
|
265
|
+
|
|
266
|
+
• "Run my expense report" — categorize and summarize expenses
|
|
267
|
+
• "Generate a P&L" — Profit & Loss statement
|
|
268
|
+
• "Create an invoice" — generate a formatted invoice
|
|
269
|
+
• "Run payroll" — calculate gross, taxes, deductions, net pay
|
|
270
|
+
• "Show me AR aging" — who owes money and how overdue
|
|
271
|
+
• "Show me AP aging" — what we owe vendors
|
|
272
|
+
• "Reconcile the bank" — match bank vs book transactions
|
|
273
|
+
• "Budget vs actuals" — compare spending to budget
|
|
274
|
+
• "Calculate sales tax" — multi-state tax tracking
|
|
275
|
+
• "Depreciation schedule" — fixed asset depreciation
|
|
276
|
+
• "Cash flow statement" — operating/investing/financing
|
|
277
|
+
• "Department spending" — spending by department
|
|
278
|
+
• "Variance analysis" — find significant budget variances
|
|
279
|
+
• "W-2 and 1099 prep" — year-end tax form compilation
|
|
280
|
+
• "Make a chart" — visualize any data
|
|
281
|
+
|
|
282
|
+
Just tell me what you need!`;
|
|
283
|
+
}
|
|
284
|
+
if (/hi|hello|hey/.test(userText)) {
|
|
285
|
+
return "Hi! I'm your accounting bot. I can run reports, calculate payroll, reconcile accounts, and more. Just tell me what you need — for example, \"run my expense report\" or \"show me the P&L.\"";
|
|
286
|
+
}
|
|
287
|
+
return "I can help with that. Try asking me to run a specific report like \"expense report\", \"P&L\", \"payroll\", or \"AR aging\". Type \"help\" to see everything I can do.";
|
|
288
|
+
}
|
|
289
|
+
function formatConversation(messages) {
|
|
290
|
+
const parts = [];
|
|
291
|
+
for (const msg of messages) {
|
|
292
|
+
if (msg.role === "system")
|
|
293
|
+
parts.push(`Instructions: ${msg.content}\n`);
|
|
294
|
+
else if (msg.role === "user")
|
|
295
|
+
parts.push(`User: ${msg.content}`);
|
|
296
|
+
else if (msg.role === "assistant")
|
|
297
|
+
parts.push(`Assistant: ${msg.content}`);
|
|
298
|
+
}
|
|
299
|
+
parts.push("Assistant:");
|
|
300
|
+
return parts.join("\n");
|
|
301
|
+
}
|
|
302
|
+
function cleanResponse(text) {
|
|
303
|
+
let cleaned = text.trim();
|
|
304
|
+
const userIdx = cleaned.indexOf("\nUser:");
|
|
305
|
+
if (userIdx > 0)
|
|
306
|
+
cleaned = cleaned.slice(0, userIdx).trim();
|
|
307
|
+
if (cleaned.startsWith("Assistant:"))
|
|
308
|
+
cleaned = cleaned.slice(10).trim();
|
|
309
|
+
if (cleaned.length > 2000)
|
|
310
|
+
cleaned = cleaned.slice(0, 2000) + "...";
|
|
311
|
+
return cleaned || "I understand. How can I help you?";
|
|
312
|
+
}
|
|
313
|
+
function detectPreferences(messages) {
|
|
314
|
+
const updates = [];
|
|
315
|
+
const lastUser = messages.filter(m => m.role === "user").pop();
|
|
316
|
+
if (!lastUser)
|
|
317
|
+
return updates;
|
|
318
|
+
const lower = lastUser.content.toLowerCase();
|
|
319
|
+
if (/always sort|sort by|order by/.test(lower)) {
|
|
320
|
+
const match = lower.match(/sort (?:by|expenses by) (\w+)/);
|
|
321
|
+
if (match)
|
|
322
|
+
updates.push({ key: "sort_preference", value: match[1] });
|
|
323
|
+
}
|
|
324
|
+
if (/always include|i want|i prefer|i like/.test(lower)) {
|
|
325
|
+
if (/department/.test(lower))
|
|
326
|
+
updates.push({ key: "include_departments", value: "true" });
|
|
327
|
+
if (/chart/.test(lower))
|
|
328
|
+
updates.push({ key: "include_charts", value: "true" });
|
|
329
|
+
}
|
|
330
|
+
if (/my company|our company|we are|i work at/.test(lower)) {
|
|
331
|
+
const match = lower.match(/(?:company is|work at|we are) ([^,.]+)/);
|
|
332
|
+
if (match)
|
|
333
|
+
updates.push({ key: "company_name", value: match[1].trim() });
|
|
334
|
+
}
|
|
335
|
+
return updates;
|
|
336
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleDepartmentSpending = handleDepartmentSpending;
|
|
4
|
+
async function handleDepartmentSpending(task) {
|
|
5
|
+
const start = Date.now();
|
|
6
|
+
const input = task.input_payload;
|
|
7
|
+
const period = input.period || "Current Period";
|
|
8
|
+
const expenses = input.expenses || [
|
|
9
|
+
{ department: "Engineering", category: "Salaries", amount: 85000 },
|
|
10
|
+
{ department: "Engineering", category: "Software", amount: 12000 },
|
|
11
|
+
{ department: "Engineering", category: "Hardware", amount: 8500 },
|
|
12
|
+
{ department: "Marketing", category: "Salaries", amount: 45000 },
|
|
13
|
+
{ department: "Marketing", category: "Advertising", amount: 25000 },
|
|
14
|
+
{ department: "Marketing", category: "Events", amount: 8000 },
|
|
15
|
+
{ department: "Sales", category: "Salaries", amount: 62000 },
|
|
16
|
+
{ department: "Sales", category: "Travel", amount: 15000 },
|
|
17
|
+
{ department: "Sales", category: "Entertainment", amount: 5000 },
|
|
18
|
+
{ department: "Operations", category: "Rent", amount: 8500 },
|
|
19
|
+
{ department: "Operations", category: "Utilities", amount: 2500 },
|
|
20
|
+
{ department: "Operations", category: "Supplies", amount: 1800 },
|
|
21
|
+
{ department: "HR", category: "Salaries", amount: 35000 },
|
|
22
|
+
{ department: "HR", category: "Benefits Admin", amount: 4500 },
|
|
23
|
+
{ department: "HR", category: "Recruiting", amount: 7500 },
|
|
24
|
+
];
|
|
25
|
+
let grandTotal = 0;
|
|
26
|
+
const byDept = {};
|
|
27
|
+
for (const exp of expenses) {
|
|
28
|
+
grandTotal += exp.amount;
|
|
29
|
+
if (!byDept[exp.department])
|
|
30
|
+
byDept[exp.department] = { total: 0, categories: {}, count: 0 };
|
|
31
|
+
byDept[exp.department].total += exp.amount;
|
|
32
|
+
byDept[exp.department].count += 1;
|
|
33
|
+
byDept[exp.department].categories[exp.category] = (byDept[exp.department].categories[exp.category] || 0) + exp.amount;
|
|
34
|
+
}
|
|
35
|
+
grandTotal = round(grandTotal);
|
|
36
|
+
const deptSummary = Object.entries(byDept)
|
|
37
|
+
.sort((a, b) => b[1].total - a[1].total)
|
|
38
|
+
.map(([name, data]) => ({
|
|
39
|
+
department: name,
|
|
40
|
+
total: round(data.total),
|
|
41
|
+
pct: round((data.total / grandTotal) * 100),
|
|
42
|
+
count: data.count,
|
|
43
|
+
categories: Object.entries(data.categories).sort((a, b) => b[1] - a[1]).map(([cat, amt]) => ({ category: cat, amount: round(amt) })),
|
|
44
|
+
}));
|
|
45
|
+
const csv = [
|
|
46
|
+
"DEPARTMENT SPENDING REPORT",
|
|
47
|
+
`Period: ${period}`,
|
|
48
|
+
`Generated: ${new Date().toISOString().split("T")[0]}`,
|
|
49
|
+
`Grand Total: ${fmt(grandTotal)}`,
|
|
50
|
+
"",
|
|
51
|
+
"SUMMARY BY DEPARTMENT",
|
|
52
|
+
"Department,Total Spending,% of Total,Line Items",
|
|
53
|
+
...deptSummary.map(d => `${esc(d.department)},${fmt(d.total)},${d.pct}%,${d.count}`),
|
|
54
|
+
"",
|
|
55
|
+
];
|
|
56
|
+
// Add breakdown for each department
|
|
57
|
+
for (const dept of deptSummary) {
|
|
58
|
+
csv.push(`${dept.department.toUpperCase()} BREAKDOWN`);
|
|
59
|
+
csv.push("Category,Amount,% of Department");
|
|
60
|
+
for (const cat of dept.categories) {
|
|
61
|
+
csv.push(`${esc(cat.category)},${fmt(cat.amount)},${round((cat.amount / dept.total) * 100)}%`);
|
|
62
|
+
}
|
|
63
|
+
csv.push(`Department Total,${fmt(dept.total)},100%`);
|
|
64
|
+
csv.push("");
|
|
65
|
+
}
|
|
66
|
+
csv.push("GRAND TOTAL");
|
|
67
|
+
csv.push(`Total All Departments,${fmt(grandTotal)}`);
|
|
68
|
+
csv.push(`Total Departments,${deptSummary.length}`);
|
|
69
|
+
csv.push(`Total Line Items,${expenses.length}`);
|
|
70
|
+
csv.push(`Largest Department,${deptSummary[0]?.department || "N/A"} (${fmt(deptSummary[0]?.total || 0)})`);
|
|
71
|
+
csv.push(`Smallest Department,${deptSummary[deptSummary.length - 1]?.department || "N/A"} (${fmt(deptSummary[deptSummary.length - 1]?.total || 0)})`);
|
|
72
|
+
return {
|
|
73
|
+
text: `Department Spending: ${deptSummary.length} departments | Total: ${fmt(grandTotal)} | Largest: ${deptSummary[0]?.department}`,
|
|
74
|
+
output_csv: csv.join("\n"),
|
|
75
|
+
summary: { grand_total: grandTotal, department_count: deptSummary.length },
|
|
76
|
+
by_department: deptSummary,
|
|
77
|
+
chart_data: { chart_type: "pie", title: `Spending by Department — ${period}`, labels: deptSummary.map(d => d.department), datasets: [{ label: "Spending", data: deptSummary.map(d => d.total) }] },
|
|
78
|
+
duration_ms: Date.now() - start,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function round(n) { return Math.round(n * 100) / 100; }
|
|
82
|
+
function fmt(n) { return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }
|
|
83
|
+
function esc(v) { return v.includes(",") ? `"${v}"` : v; }
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleDepreciation = handleDepreciation;
|
|
4
|
+
async function handleDepreciation(task) {
|
|
5
|
+
const start = Date.now();
|
|
6
|
+
const input = task.input_payload;
|
|
7
|
+
const asOf = input.as_of_date ? new Date(input.as_of_date) : new Date();
|
|
8
|
+
const assets = input.assets || [
|
|
9
|
+
{ name: "Office Furniture", cost: 15000, salvage_value: 1500, useful_life_years: 7, date_acquired: "2023-06-15", method: "straight-line" },
|
|
10
|
+
{ name: "MacBook Pro Fleet (10)", cost: 35000, salvage_value: 5000, useful_life_years: 3, date_acquired: "2025-01-10", method: "straight-line" },
|
|
11
|
+
{ name: "Server Equipment", cost: 28000, salvage_value: 3000, useful_life_years: 5, date_acquired: "2024-03-01", method: "double-declining" },
|
|
12
|
+
{ name: "Company Vehicle", cost: 42000, salvage_value: 8000, useful_life_years: 5, date_acquired: "2024-09-01", method: "straight-line" },
|
|
13
|
+
{ name: "HVAC System", cost: 18000, salvage_value: 2000, useful_life_years: 10, date_acquired: "2022-01-01", method: "straight-line" },
|
|
14
|
+
];
|
|
15
|
+
let totalCost = 0, totalAccumDep = 0, totalCurrentYearDep = 0;
|
|
16
|
+
const schedule = assets.map(asset => {
|
|
17
|
+
const method = asset.method || "straight-line";
|
|
18
|
+
const depreciable = asset.cost - asset.salvage_value;
|
|
19
|
+
const acquired = new Date(asset.date_acquired);
|
|
20
|
+
const monthsOwned = Math.max(0, (asOf.getFullYear() - acquired.getFullYear()) * 12 + (asOf.getMonth() - acquired.getMonth()));
|
|
21
|
+
const yearsOwned = monthsOwned / 12;
|
|
22
|
+
let annualDep;
|
|
23
|
+
let accumDep;
|
|
24
|
+
let currentYearDep;
|
|
25
|
+
if (method === "straight-line") {
|
|
26
|
+
annualDep = round(depreciable / asset.useful_life_years);
|
|
27
|
+
accumDep = round(Math.min(annualDep * yearsOwned, depreciable));
|
|
28
|
+
currentYearDep = yearsOwned >= asset.useful_life_years ? 0 : annualDep;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
// Double declining balance
|
|
32
|
+
const rate = 2 / asset.useful_life_years;
|
|
33
|
+
let bookValue = asset.cost;
|
|
34
|
+
accumDep = 0;
|
|
35
|
+
currentYearDep = 0;
|
|
36
|
+
for (let y = 0; y < Math.ceil(yearsOwned); y++) {
|
|
37
|
+
const dep = Math.min(round(bookValue * rate), bookValue - asset.salvage_value);
|
|
38
|
+
if (dep <= 0)
|
|
39
|
+
break;
|
|
40
|
+
if (y < Math.floor(yearsOwned)) {
|
|
41
|
+
accumDep += dep;
|
|
42
|
+
bookValue -= dep;
|
|
43
|
+
}
|
|
44
|
+
if (y === Math.floor(yearsOwned)) {
|
|
45
|
+
currentYearDep = dep;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
annualDep = round(asset.cost * (2 / asset.useful_life_years));
|
|
49
|
+
accumDep = round(accumDep);
|
|
50
|
+
}
|
|
51
|
+
const bookValue = round(asset.cost - accumDep);
|
|
52
|
+
const fullyDepreciated = accumDep >= depreciable;
|
|
53
|
+
const remainingLife = Math.max(0, round(asset.useful_life_years - yearsOwned));
|
|
54
|
+
totalCost += asset.cost;
|
|
55
|
+
totalAccumDep += accumDep;
|
|
56
|
+
totalCurrentYearDep += currentYearDep;
|
|
57
|
+
return { name: asset.name, cost: asset.cost, salvage: asset.salvage_value, useful_life: asset.useful_life_years, method, date_acquired: asset.date_acquired, annual_depreciation: annualDep, accumulated_depreciation: accumDep, current_year_depreciation: round(currentYearDep), book_value: bookValue, remaining_life: remainingLife, fully_depreciated: fullyDepreciated };
|
|
58
|
+
});
|
|
59
|
+
totalCost = round(totalCost);
|
|
60
|
+
totalAccumDep = round(totalAccumDep);
|
|
61
|
+
totalCurrentYearDep = round(totalCurrentYearDep);
|
|
62
|
+
const totalBookValue = round(totalCost - totalAccumDep);
|
|
63
|
+
const csv = [
|
|
64
|
+
"DEPRECIATION SCHEDULE",
|
|
65
|
+
`As of: ${asOf.toISOString().split("T")[0]}`,
|
|
66
|
+
"",
|
|
67
|
+
"ASSET DETAIL",
|
|
68
|
+
"Asset,Cost,Salvage,Useful Life,Method,Date Acquired,Annual Dep,Accum Dep,Book Value,Remaining Life,Status",
|
|
69
|
+
...schedule.map(a => `${esc(a.name)},${fmt(a.cost)},${fmt(a.salvage)},${a.useful_life} yr,${a.method},${a.date_acquired},${fmt(a.annual_depreciation)},${fmt(a.accumulated_depreciation)},${fmt(a.book_value)},${a.remaining_life.toFixed(1)} yr,${a.fully_depreciated ? "Fully Depreciated" : "Active"}`),
|
|
70
|
+
"",
|
|
71
|
+
"CURRENT YEAR DEPRECIATION EXPENSE",
|
|
72
|
+
"Asset,Current Year Depreciation",
|
|
73
|
+
...schedule.filter(a => a.current_year_depreciation > 0).map(a => `${esc(a.name)},${fmt(a.current_year_depreciation)}`),
|
|
74
|
+
`Total Current Year,${fmt(totalCurrentYearDep)}`,
|
|
75
|
+
"",
|
|
76
|
+
"SUMMARY",
|
|
77
|
+
`Total Asset Cost,${fmt(totalCost)}`,
|
|
78
|
+
`Total Accumulated Depreciation,${fmt(totalAccumDep)}`,
|
|
79
|
+
`Total Book Value,${fmt(totalBookValue)}`,
|
|
80
|
+
`Current Year Depreciation Expense,${fmt(totalCurrentYearDep)}`,
|
|
81
|
+
`Active Assets,${schedule.filter(a => !a.fully_depreciated).length}`,
|
|
82
|
+
`Fully Depreciated Assets,${schedule.filter(a => a.fully_depreciated).length}`,
|
|
83
|
+
];
|
|
84
|
+
return {
|
|
85
|
+
text: `Depreciation Schedule: ${schedule.length} assets | Book Value: ${fmt(totalBookValue)} | Current Year: ${fmt(totalCurrentYearDep)}`,
|
|
86
|
+
output_csv: csv.join("\n"),
|
|
87
|
+
summary: { total_cost: totalCost, total_accum_dep: totalAccumDep, total_book_value: totalBookValue, current_year: totalCurrentYearDep, asset_count: schedule.length },
|
|
88
|
+
duration_ms: Date.now() - start,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function round(n) { return Math.round(n * 100) / 100; }
|
|
92
|
+
function fmt(n) { return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }
|
|
93
|
+
function esc(v) { return v.includes(",") ? `"${v}"` : v; }
|