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
package/dist/handlers/expense.js
CHANGED
|
@@ -1,38 +1,82 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.handleExpenseReport = handleExpenseReport;
|
|
4
|
+
/**
|
|
5
|
+
* Format a date string to YYYY-MM-DD for proper Excel sorting.
|
|
6
|
+
*/
|
|
7
|
+
function normalizeDate(dateStr) {
|
|
8
|
+
if (!dateStr)
|
|
9
|
+
return "";
|
|
10
|
+
// Already YYYY-MM-DD
|
|
11
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr))
|
|
12
|
+
return dateStr;
|
|
13
|
+
// M/D/YYYY or MM/DD/YYYY
|
|
14
|
+
const match = dateStr.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
|
|
15
|
+
if (match)
|
|
16
|
+
return `${match[3]}-${match[1].padStart(2, "0")}-${match[2].padStart(2, "0")}`;
|
|
17
|
+
return dateStr;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Format a number as currency without trailing spaces.
|
|
21
|
+
*/
|
|
22
|
+
function fmtCurrency(n) {
|
|
23
|
+
return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Detect department from filename for merged file processing.
|
|
27
|
+
*/
|
|
28
|
+
function detectDepartment(fileName) {
|
|
29
|
+
const lower = fileName.toLowerCase();
|
|
30
|
+
if (/marketing/.test(lower))
|
|
31
|
+
return "Marketing";
|
|
32
|
+
if (/operation/.test(lower))
|
|
33
|
+
return "Operations";
|
|
34
|
+
if (/tech/.test(lower))
|
|
35
|
+
return "Technology";
|
|
36
|
+
if (/hr|payroll|human/.test(lower))
|
|
37
|
+
return "HR & Payroll";
|
|
38
|
+
if (/travel|entertainment/.test(lower))
|
|
39
|
+
return "Travel & Entertainment";
|
|
40
|
+
if (/sales/.test(lower))
|
|
41
|
+
return "Sales";
|
|
42
|
+
if (/admin/.test(lower))
|
|
43
|
+
return "Administration";
|
|
44
|
+
return "General";
|
|
45
|
+
}
|
|
4
46
|
async function handleExpenseReport(task) {
|
|
5
47
|
const start = Date.now();
|
|
6
48
|
const input = task.input_payload;
|
|
7
49
|
const period = input.period || "Current Period";
|
|
50
|
+
const mergedFrom = input.merged_from || [];
|
|
8
51
|
const expenses = input.expenses || [
|
|
9
52
|
{ description: "Office Supplies", amount: 245.50, category: "Operations", date: "2026-03-01" },
|
|
10
53
|
{ description: "Software Subscriptions", amount: 890.00, category: "Technology", date: "2026-03-05" },
|
|
11
54
|
{ description: "Team Lunch", amount: 156.75, category: "Meals", date: "2026-03-08" },
|
|
12
|
-
{ description: "Cloud Hosting", amount: 432.00, category: "Technology", date: "2026-03-10" },
|
|
13
|
-
{ description: "Marketing Ads", amount: 1200.00, category: "Marketing", date: "2026-03-12" },
|
|
14
|
-
{ description: "Travel - Client Meeting", amount: 387.25, category: "Travel", date: "2026-03-15" },
|
|
15
|
-
{ description: "Printer Ink", amount: 89.99, category: "Operations", date: "2026-03-18" },
|
|
16
|
-
{ description: "Conference Tickets", amount: 599.00, category: "Education", date: "2026-03-20" },
|
|
17
55
|
];
|
|
56
|
+
// Assign departments from merged file names if available
|
|
57
|
+
if (mergedFrom.length > 0 && expenses.every((e) => !e.department)) {
|
|
58
|
+
// Try to detect department from category patterns
|
|
59
|
+
for (const exp of expenses) {
|
|
60
|
+
exp.department = detectDepartmentFromCategory(exp.category);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Sort by amount descending
|
|
64
|
+
const sorted = [...expenses].sort((a, b) => b.amount - a.amount);
|
|
18
65
|
// Calculate totals by category
|
|
19
66
|
const byCategory = {};
|
|
20
67
|
let total = 0;
|
|
21
68
|
for (const exp of expenses) {
|
|
22
69
|
total += exp.amount;
|
|
23
70
|
if (!byCategory[exp.category]) {
|
|
24
|
-
byCategory[exp.category] = { total: 0, count: 0
|
|
71
|
+
byCategory[exp.category] = { total: 0, count: 0 };
|
|
25
72
|
}
|
|
26
73
|
byCategory[exp.category].total += exp.amount;
|
|
27
74
|
byCategory[exp.category].count += 1;
|
|
28
|
-
byCategory[exp.category].items.push(exp);
|
|
29
75
|
}
|
|
30
|
-
// Round totals
|
|
31
76
|
total = Math.round(total * 100) / 100;
|
|
32
77
|
for (const cat of Object.keys(byCategory)) {
|
|
33
78
|
byCategory[cat].total = Math.round(byCategory[cat].total * 100) / 100;
|
|
34
79
|
}
|
|
35
|
-
// Sort categories by total descending
|
|
36
80
|
const sortedCategories = Object.entries(byCategory)
|
|
37
81
|
.sort((a, b) => b[1].total - a[1].total)
|
|
38
82
|
.map(([name, data]) => ({
|
|
@@ -41,41 +85,110 @@ async function handleExpenseReport(task) {
|
|
|
41
85
|
count: data.count,
|
|
42
86
|
percentage: Math.round((data.total / total) * 1000) / 10,
|
|
43
87
|
}));
|
|
44
|
-
//
|
|
88
|
+
// Calculate by department if available
|
|
89
|
+
const hasDepartments = sorted.some((e) => e.department);
|
|
90
|
+
const byDepartment = {};
|
|
91
|
+
if (hasDepartments) {
|
|
92
|
+
for (const exp of expenses) {
|
|
93
|
+
const dept = exp.department || "General";
|
|
94
|
+
if (!byDepartment[dept])
|
|
95
|
+
byDepartment[dept] = { total: 0, count: 0 };
|
|
96
|
+
byDepartment[dept].total += exp.amount;
|
|
97
|
+
byDepartment[dept].count += 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const sortedDepartments = Object.entries(byDepartment)
|
|
101
|
+
.sort((a, b) => b[1].total - a[1].total)
|
|
102
|
+
.map(([name, data]) => ({
|
|
103
|
+
department: name,
|
|
104
|
+
total: Math.round(data.total * 100) / 100,
|
|
105
|
+
count: data.count,
|
|
106
|
+
percentage: Math.round((data.total / total) * 1000) / 10,
|
|
107
|
+
}));
|
|
108
|
+
// Chart data
|
|
45
109
|
const chartData = {
|
|
46
110
|
chart_type: "doughnut",
|
|
47
111
|
title: `Expenses by Category — ${period}`,
|
|
48
112
|
labels: sortedCategories.map((c) => c.category),
|
|
49
|
-
datasets: [{
|
|
50
|
-
label: "Expenses",
|
|
51
|
-
data: sortedCategories.map((c) => c.total),
|
|
52
|
-
}],
|
|
113
|
+
datasets: [{ label: "Expenses", data: sortedCategories.map((c) => c.total) }],
|
|
53
114
|
};
|
|
54
115
|
// Summary text
|
|
55
116
|
const summaryLines = [
|
|
56
117
|
`Expense Report: ${period}`,
|
|
57
|
-
`Total:
|
|
118
|
+
`Total: ${fmtCurrency(total)} across ${expenses.length} expenses in ${sortedCategories.length} categories`,
|
|
58
119
|
``,
|
|
59
|
-
`
|
|
60
|
-
...sortedCategories.map((c) => ` ${c.category}:
|
|
120
|
+
`Top Categories:`,
|
|
121
|
+
...sortedCategories.slice(0, 10).map((c) => ` ${c.category}: ${fmtCurrency(c.total)} (${c.percentage}%) — ${c.count} items`),
|
|
61
122
|
``,
|
|
62
|
-
`Largest expense: ${
|
|
63
|
-
|
|
64
|
-
// Generate CSV output
|
|
65
|
-
const csvLines = [
|
|
66
|
-
"EXPENSE REPORT",
|
|
67
|
-
`Period,${period}`,
|
|
68
|
-
"",
|
|
69
|
-
"EXPENSES BY ITEM",
|
|
70
|
-
"Date,Description,Amount,Category",
|
|
71
|
-
...expenses.map((e) => `${e.date || ""},${csvEscape(e.description)},$${e.amount.toFixed(2)},${csvEscape(e.category)}`),
|
|
72
|
-
"",
|
|
73
|
-
"SUMMARY BY CATEGORY",
|
|
74
|
-
"Category,Total,% of Total,Item Count",
|
|
75
|
-
...sortedCategories.map((c) => `${csvEscape(c.category)},$${c.total.toFixed(2)},${c.percentage}%,${c.count}`),
|
|
76
|
-
"",
|
|
77
|
-
`TOTAL,,$${total.toFixed(2)},${expenses.length} items`,
|
|
123
|
+
`Largest expense: ${sorted[0].description} (${fmtCurrency(sorted[0].amount)})`,
|
|
124
|
+
`Smallest expense: ${sorted[sorted.length - 1].description} (${fmtCurrency(sorted[sorted.length - 1].amount)})`,
|
|
78
125
|
];
|
|
126
|
+
// ── Build clean CSV output ────────────────────────────────
|
|
127
|
+
const csvLines = [];
|
|
128
|
+
const hasDept = hasDepartments;
|
|
129
|
+
const itemHeaders = hasDept
|
|
130
|
+
? "Date,Department,Description,Category,Amount"
|
|
131
|
+
: "Date,Description,Category,Amount";
|
|
132
|
+
// Report header
|
|
133
|
+
csvLines.push("EXPENSE REPORT");
|
|
134
|
+
csvLines.push(`Period: ${period}`);
|
|
135
|
+
csvLines.push(`Generated: ${new Date().toISOString().split("T")[0]}`);
|
|
136
|
+
if (mergedFrom.length > 0) {
|
|
137
|
+
csvLines.push(`Sources: ${mergedFrom.length} files merged`);
|
|
138
|
+
}
|
|
139
|
+
csvLines.push(`Total Items: ${expenses.length}`);
|
|
140
|
+
csvLines.push(`Grand Total: ${fmtCurrency(total)}`);
|
|
141
|
+
csvLines.push("");
|
|
142
|
+
// Section 1: All expenses sorted by amount
|
|
143
|
+
csvLines.push("DETAILED EXPENSES (sorted by amount)");
|
|
144
|
+
csvLines.push(itemHeaders);
|
|
145
|
+
for (const exp of sorted) {
|
|
146
|
+
const date = normalizeDate(exp.date);
|
|
147
|
+
const desc = csvEscape(exp.description);
|
|
148
|
+
const cat = csvEscape(exp.category);
|
|
149
|
+
const amt = fmtCurrency(exp.amount);
|
|
150
|
+
if (hasDept) {
|
|
151
|
+
csvLines.push(`${date},${csvEscape(exp.department || "General")},${desc},${cat},${amt}`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
csvLines.push(`${date},${desc},${cat},${amt}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Subtotal row
|
|
158
|
+
if (hasDept) {
|
|
159
|
+
csvLines.push(`,,,,`);
|
|
160
|
+
csvLines.push(`,,,SUBTOTAL,${fmtCurrency(total)}`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
csvLines.push(`,,,`);
|
|
164
|
+
csvLines.push(`,,SUBTOTAL,${fmtCurrency(total)}`);
|
|
165
|
+
}
|
|
166
|
+
csvLines.push("");
|
|
167
|
+
// Section 2: Summary by category
|
|
168
|
+
csvLines.push("SUMMARY BY CATEGORY");
|
|
169
|
+
csvLines.push("Category,Amount,% of Total,Item Count");
|
|
170
|
+
for (const c of sortedCategories) {
|
|
171
|
+
csvLines.push(`${csvEscape(c.category)},${fmtCurrency(c.total)},${c.percentage}%,${c.count}`);
|
|
172
|
+
}
|
|
173
|
+
csvLines.push("");
|
|
174
|
+
// Section 3: Summary by department (if merged)
|
|
175
|
+
if (hasDept && sortedDepartments.length > 0) {
|
|
176
|
+
csvLines.push("SUMMARY BY DEPARTMENT");
|
|
177
|
+
csvLines.push("Department,Amount,% of Total,Item Count");
|
|
178
|
+
for (const d of sortedDepartments) {
|
|
179
|
+
csvLines.push(`${csvEscape(d.department)},${fmtCurrency(d.total)},${d.percentage}%,${d.count}`);
|
|
180
|
+
}
|
|
181
|
+
csvLines.push("");
|
|
182
|
+
}
|
|
183
|
+
// Grand total
|
|
184
|
+
csvLines.push("GRAND TOTAL");
|
|
185
|
+
csvLines.push(`Total Expenses,${fmtCurrency(total)}`);
|
|
186
|
+
csvLines.push(`Total Items,${expenses.length}`);
|
|
187
|
+
csvLines.push(`Total Categories,${sortedCategories.length}`);
|
|
188
|
+
if (hasDept)
|
|
189
|
+
csvLines.push(`Total Departments,${sortedDepartments.length}`);
|
|
190
|
+
csvLines.push(`Largest Expense,${csvEscape(sorted[0].description)} (${fmtCurrency(sorted[0].amount)})`);
|
|
191
|
+
csvLines.push(`Average Expense,${fmtCurrency(Math.round((total / expenses.length) * 100) / 100)}`);
|
|
79
192
|
const durationMs = Date.now() - start;
|
|
80
193
|
return {
|
|
81
194
|
text: summaryLines.join("\n"),
|
|
@@ -86,10 +199,12 @@ async function handleExpenseReport(task) {
|
|
|
86
199
|
expense_count: expenses.length,
|
|
87
200
|
category_count: sortedCategories.length,
|
|
88
201
|
largest_category: sortedCategories[0]?.category,
|
|
89
|
-
largest_expense:
|
|
202
|
+
largest_expense: sorted[0],
|
|
203
|
+
average_expense: Math.round((total / expenses.length) * 100) / 100,
|
|
90
204
|
},
|
|
91
205
|
by_category: sortedCategories,
|
|
92
|
-
|
|
206
|
+
by_department: sortedDepartments.length > 0 ? sortedDepartments : undefined,
|
|
207
|
+
expenses: sorted,
|
|
93
208
|
chart_data: chartData,
|
|
94
209
|
duration_ms: durationMs,
|
|
95
210
|
};
|
|
@@ -100,3 +215,19 @@ function csvEscape(value) {
|
|
|
100
215
|
}
|
|
101
216
|
return value;
|
|
102
217
|
}
|
|
218
|
+
function detectDepartmentFromCategory(category) {
|
|
219
|
+
const lower = category.toLowerCase();
|
|
220
|
+
if (/payroll|benefits|recruiting|onboarding|training|employee|hr|insurance|safety/.test(lower))
|
|
221
|
+
return "HR & Payroll";
|
|
222
|
+
if (/digital|marketing|pr|creative|print|email|influencer|affiliate|content|seo|advertising/.test(lower))
|
|
223
|
+
return "Marketing";
|
|
224
|
+
if (/cloud|software|hardware|ai|infrastructure|security/.test(lower))
|
|
225
|
+
return "Technology";
|
|
226
|
+
if (/rent|utilities|maintenance|office supplies|equipment|furniture/.test(lower))
|
|
227
|
+
return "Operations";
|
|
228
|
+
if (/airfare|lodging|hotel|transport|conference|entertainment|team meal/.test(lower))
|
|
229
|
+
return "Travel & Entertainment";
|
|
230
|
+
if (/consulting/.test(lower))
|
|
231
|
+
return "Consulting";
|
|
232
|
+
return "General";
|
|
233
|
+
}
|
|
@@ -29,7 +29,7 @@ async function handleFileProcess(task) {
|
|
|
29
29
|
if (expenses.length > 0) {
|
|
30
30
|
const result = await (0, expense_js_1.handleExpenseReport)({
|
|
31
31
|
type: "expense_report",
|
|
32
|
-
input_payload: { expenses },
|
|
32
|
+
input_payload: { expenses, merged_from: payload.merged_from },
|
|
33
33
|
});
|
|
34
34
|
return {
|
|
35
35
|
...result,
|
package/dist/handlers/index.d.ts
CHANGED
|
@@ -9,12 +9,6 @@ export interface TaskOutput {
|
|
|
9
9
|
}
|
|
10
10
|
export type TaskHandler = (input: TaskInput) => Promise<TaskOutput>;
|
|
11
11
|
declare const handlers: Record<string, TaskHandler>;
|
|
12
|
-
/**
|
|
13
|
-
* Check if we have a specialized handler for this task type.
|
|
14
|
-
*/
|
|
15
12
|
export declare function hasHandler(type: string): boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Run the specialized handler for a task type.
|
|
18
|
-
*/
|
|
19
13
|
export declare function runHandler(task: TaskInput): Promise<TaskOutput>;
|
|
20
14
|
export { handlers };
|
package/dist/handlers/index.js
CHANGED
|
@@ -9,24 +9,44 @@ const expense_js_1 = require("./expense.js");
|
|
|
9
9
|
const pnl_js_1 = require("./pnl.js");
|
|
10
10
|
const smart_route_js_1 = require("./smart-route.js");
|
|
11
11
|
const file_processor_js_1 = require("./file-processor.js");
|
|
12
|
+
const chat_js_1 = require("./chat.js");
|
|
13
|
+
const ar_aging_js_1 = require("./ar-aging.js");
|
|
14
|
+
const ap_aging_js_1 = require("./ap-aging.js");
|
|
15
|
+
const bank_reconciliation_js_1 = require("./bank-reconciliation.js");
|
|
16
|
+
const budget_vs_actuals_js_1 = require("./budget-vs-actuals.js");
|
|
17
|
+
const payroll_js_1 = require("./payroll.js");
|
|
18
|
+
const sales_tax_js_1 = require("./sales-tax.js");
|
|
19
|
+
const depreciation_js_1 = require("./depreciation.js");
|
|
20
|
+
const cash_flow_js_1 = require("./cash-flow.js");
|
|
21
|
+
const department_spending_js_1 = require("./department-spending.js");
|
|
22
|
+
const variance_analysis_js_1 = require("./variance-analysis.js");
|
|
23
|
+
const w2_1099_js_1 = require("./w2-1099.js");
|
|
12
24
|
const handlers = {
|
|
25
|
+
// Original handlers
|
|
13
26
|
chart: chart_js_1.handleChart,
|
|
14
27
|
invoice: invoice_js_1.handleInvoice,
|
|
15
28
|
expense_report: expense_js_1.handleExpenseReport,
|
|
16
29
|
pnl: pnl_js_1.handlePnl,
|
|
17
30
|
smart_route: smart_route_js_1.handleSmartRoute,
|
|
18
31
|
process_file: file_processor_js_1.handleFileProcess,
|
|
32
|
+
chat: chat_js_1.handleChat,
|
|
33
|
+
// Tier 1 accounting handlers
|
|
34
|
+
ar_aging: ar_aging_js_1.handleARAging,
|
|
35
|
+
ap_aging: ap_aging_js_1.handleAPAging,
|
|
36
|
+
bank_reconciliation: bank_reconciliation_js_1.handleBankReconciliation,
|
|
37
|
+
budget_vs_actuals: budget_vs_actuals_js_1.handleBudgetVsActuals,
|
|
38
|
+
payroll: payroll_js_1.handlePayroll,
|
|
39
|
+
sales_tax: sales_tax_js_1.handleSalesTax,
|
|
40
|
+
depreciation: depreciation_js_1.handleDepreciation,
|
|
41
|
+
cash_flow: cash_flow_js_1.handleCashFlow,
|
|
42
|
+
department_spending: department_spending_js_1.handleDepartmentSpending,
|
|
43
|
+
variance_analysis: variance_analysis_js_1.handleVarianceAnalysis,
|
|
44
|
+
w2_1099: w2_1099_js_1.handleW21099,
|
|
19
45
|
};
|
|
20
46
|
exports.handlers = handlers;
|
|
21
|
-
/**
|
|
22
|
-
* Check if we have a specialized handler for this task type.
|
|
23
|
-
*/
|
|
24
47
|
function hasHandler(type) {
|
|
25
48
|
return type in handlers;
|
|
26
49
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Run the specialized handler for a task type.
|
|
29
|
-
*/
|
|
30
50
|
async function runHandler(task) {
|
|
31
51
|
const handler = handlers[task.type];
|
|
32
52
|
if (!handler) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handlePayroll = handlePayroll;
|
|
4
|
+
async function handlePayroll(task) {
|
|
5
|
+
const start = Date.now();
|
|
6
|
+
const input = task.input_payload;
|
|
7
|
+
const period = input.pay_period || "Bi-Weekly";
|
|
8
|
+
const fedRate = input.federal_tax_rate || 0.22;
|
|
9
|
+
const stateRate = input.state_tax_rate || 0.05;
|
|
10
|
+
const ficaRate = input.fica_rate || 0.062;
|
|
11
|
+
const medicareRate = input.medicare_rate || 0.0145;
|
|
12
|
+
const periodsPerYear = period === "Weekly" ? 52 : period === "Bi-Weekly" ? 26 : period === "Semi-Monthly" ? 24 : 12;
|
|
13
|
+
const employees = input.employees || [
|
|
14
|
+
{ name: "John Smith", type: "salary", rate: 85000, deductions: [{ name: "Health Insurance", amount: 250 }, { name: "401k (6%)", amount: 196.15 }] },
|
|
15
|
+
{ name: "Jane Doe", type: "salary", rate: 72000, deductions: [{ name: "Health Insurance", amount: 250 }, { name: "401k (4%)", amount: 110.77 }] },
|
|
16
|
+
{ name: "Bob Wilson", type: "hourly", rate: 28.50, hours: 80, deductions: [{ name: "Health Insurance", amount: 180 }] },
|
|
17
|
+
{ name: "Alice Chen", type: "salary", rate: 95000, deductions: [{ name: "Health Insurance", amount: 250 }, { name: "401k (8%)", amount: 292.31 }, { name: "HSA", amount: 100 }] },
|
|
18
|
+
{ name: "Mike Torres", type: "hourly", rate: 22.00, hours: 84, deductions: [{ name: "Health Insurance", amount: 180 }] },
|
|
19
|
+
];
|
|
20
|
+
let totalGross = 0, totalFed = 0, totalState = 0, totalFica = 0, totalMedicare = 0, totalDeductions = 0, totalNet = 0, totalEmployerFica = 0, totalEmployerMedicare = 0;
|
|
21
|
+
const payStubs = employees.map(emp => {
|
|
22
|
+
const gross = emp.type === "salary" ? round(emp.rate / periodsPerYear) : round((emp.rate) * (emp.hours || 80));
|
|
23
|
+
const overtimeHours = emp.type === "hourly" && (emp.hours || 0) > 80 ? (emp.hours || 0) - 80 : 0;
|
|
24
|
+
const overtimePay = emp.type === "hourly" ? round(overtimeHours * emp.rate * 0.5) : 0;
|
|
25
|
+
const totalGrossPay = round(gross + overtimePay);
|
|
26
|
+
const fedTax = round(totalGrossPay * fedRate);
|
|
27
|
+
const stateTax = round(totalGrossPay * stateRate);
|
|
28
|
+
const ficaTax = round(totalGrossPay * ficaRate);
|
|
29
|
+
const medicareTax = round(totalGrossPay * medicareRate);
|
|
30
|
+
const totalTax = round(fedTax + stateTax + ficaTax + medicareTax);
|
|
31
|
+
const deductionTotal = round((emp.deductions || []).reduce((s, d) => s + d.amount, 0));
|
|
32
|
+
const netPay = round(totalGrossPay - totalTax - deductionTotal);
|
|
33
|
+
const employerFica = ficaTax;
|
|
34
|
+
const employerMedicare = medicareTax;
|
|
35
|
+
totalGross += totalGrossPay;
|
|
36
|
+
totalFed += fedTax;
|
|
37
|
+
totalState += stateTax;
|
|
38
|
+
totalFica += ficaTax;
|
|
39
|
+
totalMedicare += medicareTax;
|
|
40
|
+
totalDeductions += deductionTotal;
|
|
41
|
+
totalNet += netPay;
|
|
42
|
+
totalEmployerFica += employerFica;
|
|
43
|
+
totalEmployerMedicare += employerMedicare;
|
|
44
|
+
return {
|
|
45
|
+
name: emp.name, type: emp.type, hours: emp.hours, rate: emp.rate,
|
|
46
|
+
gross: totalGrossPay, overtime_pay: overtimePay,
|
|
47
|
+
federal_tax: fedTax, state_tax: stateTax, fica: ficaTax, medicare: medicareTax,
|
|
48
|
+
total_tax: totalTax, deductions: emp.deductions || [], deduction_total: deductionTotal,
|
|
49
|
+
net_pay: netPay, employer_fica: employerFica, employer_medicare: employerMedicare,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
totalGross = round(totalGross);
|
|
53
|
+
totalNet = round(totalNet);
|
|
54
|
+
totalDeductions = round(totalDeductions);
|
|
55
|
+
const totalTaxes = round(totalFed + totalState + totalFica + totalMedicare);
|
|
56
|
+
const totalEmployerCost = round(totalGross + totalEmployerFica + totalEmployerMedicare);
|
|
57
|
+
const csv = [
|
|
58
|
+
"PAYROLL REGISTER",
|
|
59
|
+
`Pay Period: ${period}`,
|
|
60
|
+
`Date: ${new Date().toISOString().split("T")[0]}`,
|
|
61
|
+
`Employees: ${employees.length}`,
|
|
62
|
+
"",
|
|
63
|
+
"EMPLOYEE DETAIL",
|
|
64
|
+
"Name,Type,Rate,Hours,Gross Pay,Federal Tax,State Tax,FICA,Medicare,Deductions,Net Pay",
|
|
65
|
+
...payStubs.map(p => `${esc(p.name)},${p.type},${p.type === "salary" ? fmt(p.rate) + "/yr" : fmt(p.rate) + "/hr"},${p.hours || "N/A"},${fmt(p.gross)},${fmt(p.federal_tax)},${fmt(p.state_tax)},${fmt(p.fica)},${fmt(p.medicare)},${fmt(p.deduction_total)},${fmt(p.net_pay)}`),
|
|
66
|
+
"",
|
|
67
|
+
`TOTALS,,,,${fmt(totalGross)},${fmt(totalFed)},${fmt(round(totalState))},${fmt(round(totalFica))},${fmt(round(totalMedicare))},${fmt(totalDeductions)},${fmt(totalNet)}`,
|
|
68
|
+
"",
|
|
69
|
+
"DEDUCTION DETAIL",
|
|
70
|
+
"Employee,Deduction,Amount",
|
|
71
|
+
...payStubs.flatMap(p => p.deductions.map(d => `${esc(p.name)},${esc(d.name)},${fmt(d.amount)}`)),
|
|
72
|
+
"",
|
|
73
|
+
"TAX SUMMARY",
|
|
74
|
+
`Federal Income Tax,${fmt(round(totalFed))}`,
|
|
75
|
+
`State Income Tax,${fmt(round(totalState))}`,
|
|
76
|
+
`Employee FICA (Social Security),${fmt(round(totalFica))}`,
|
|
77
|
+
`Employee Medicare,${fmt(round(totalMedicare))}`,
|
|
78
|
+
`Total Employee Taxes,${fmt(totalTaxes)}`,
|
|
79
|
+
"",
|
|
80
|
+
"EMPLOYER OBLIGATIONS",
|
|
81
|
+
`Employer FICA Match,${fmt(round(totalEmployerFica))}`,
|
|
82
|
+
`Employer Medicare Match,${fmt(round(totalEmployerMedicare))}`,
|
|
83
|
+
`Total Employer Taxes,${fmt(round(totalEmployerFica + totalEmployerMedicare))}`,
|
|
84
|
+
"",
|
|
85
|
+
"GRAND TOTALS",
|
|
86
|
+
`Total Gross Pay,${fmt(totalGross)}`,
|
|
87
|
+
`Total Deductions,${fmt(totalDeductions)}`,
|
|
88
|
+
`Total Taxes (Employee),${fmt(totalTaxes)}`,
|
|
89
|
+
`Total Net Pay,${fmt(totalNet)}`,
|
|
90
|
+
`Total Employer Cost,${fmt(totalEmployerCost)}`,
|
|
91
|
+
];
|
|
92
|
+
return {
|
|
93
|
+
text: `Payroll: ${employees.length} employees | Gross: ${fmt(totalGross)} | Net: ${fmt(totalNet)} | Employer Cost: ${fmt(totalEmployerCost)}`,
|
|
94
|
+
output_csv: csv.join("\n"),
|
|
95
|
+
summary: { employee_count: employees.length, total_gross: totalGross, total_net: totalNet, total_taxes: totalTaxes, total_deductions: totalDeductions, employer_cost: totalEmployerCost },
|
|
96
|
+
duration_ms: Date.now() - start,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function round(n) { return Math.round(n * 100) / 100; }
|
|
100
|
+
function fmt(n) { return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }
|
|
101
|
+
function esc(v) { return v.includes(",") ? `"${v}"` : v; }
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleSalesTax = handleSalesTax;
|
|
4
|
+
async function handleSalesTax(task) {
|
|
5
|
+
const start = Date.now();
|
|
6
|
+
const input = task.input_payload;
|
|
7
|
+
const period = input.period || "Current Period";
|
|
8
|
+
const transactions = input.transactions || [
|
|
9
|
+
{ date: "2026-03-01", description: "Product Sale - Widget A", amount: 1200, tax_rate: 0.0725, state: "CA", taxable: true },
|
|
10
|
+
{ date: "2026-03-03", description: "Consulting Service", amount: 5000, tax_rate: 0, state: "CA", taxable: false },
|
|
11
|
+
{ date: "2026-03-05", description: "Product Sale - Widget B", amount: 850, tax_rate: 0.0725, state: "CA", taxable: true },
|
|
12
|
+
{ date: "2026-03-08", description: "Product Sale - Online (TX)", amount: 2300, tax_rate: 0.0625, state: "TX", taxable: true },
|
|
13
|
+
{ date: "2026-03-12", description: "SaaS Subscription", amount: 499, tax_rate: 0, taxable: false },
|
|
14
|
+
{ date: "2026-03-15", description: "Product Sale - Widget C", amount: 3400, tax_rate: 0.0725, state: "CA", taxable: true },
|
|
15
|
+
{ date: "2026-03-20", description: "Product Sale - Online (NY)", amount: 1800, tax_rate: 0.08, state: "NY", taxable: true },
|
|
16
|
+
];
|
|
17
|
+
let totalSales = 0, totalTaxable = 0, totalExempt = 0, totalTaxCollected = 0;
|
|
18
|
+
const byState = {};
|
|
19
|
+
for (const txn of transactions) {
|
|
20
|
+
totalSales += txn.amount;
|
|
21
|
+
if (txn.taxable !== false && txn.tax_rate > 0) {
|
|
22
|
+
const tax = round(txn.amount * txn.tax_rate);
|
|
23
|
+
totalTaxable += txn.amount;
|
|
24
|
+
totalTaxCollected += tax;
|
|
25
|
+
const st = txn.state || "Unknown";
|
|
26
|
+
if (!byState[st])
|
|
27
|
+
byState[st] = { sales: 0, taxable: 0, tax: 0, rate: txn.tax_rate, count: 0 };
|
|
28
|
+
byState[st].sales += txn.amount;
|
|
29
|
+
byState[st].taxable += txn.amount;
|
|
30
|
+
byState[st].tax += tax;
|
|
31
|
+
byState[st].count += 1;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
totalExempt += txn.amount;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
totalSales = round(totalSales);
|
|
38
|
+
totalTaxable = round(totalTaxable);
|
|
39
|
+
totalExempt = round(totalExempt);
|
|
40
|
+
totalTaxCollected = round(totalTaxCollected);
|
|
41
|
+
const stateSummary = Object.entries(byState).sort((a, b) => b[1].tax - a[1].tax).map(([st, d]) => ({
|
|
42
|
+
state: st, sales: round(d.sales), taxable: round(d.taxable), tax_collected: round(d.tax), rate: d.rate, transactions: d.count,
|
|
43
|
+
}));
|
|
44
|
+
const csv = [
|
|
45
|
+
"SALES TAX REPORT",
|
|
46
|
+
`Period: ${period}`,
|
|
47
|
+
`Generated: ${new Date().toISOString().split("T")[0]}`,
|
|
48
|
+
"",
|
|
49
|
+
"SUMMARY",
|
|
50
|
+
`Total Sales,${fmt(totalSales)}`,
|
|
51
|
+
`Taxable Sales,${fmt(totalTaxable)}`,
|
|
52
|
+
`Exempt Sales,${fmt(totalExempt)}`,
|
|
53
|
+
`Total Tax Collected,${fmt(totalTaxCollected)}`,
|
|
54
|
+
`Effective Tax Rate,${totalTaxable > 0 ? round((totalTaxCollected / totalTaxable) * 100) : 0}%`,
|
|
55
|
+
"",
|
|
56
|
+
"BY STATE/JURISDICTION",
|
|
57
|
+
"State,Taxable Sales,Tax Rate,Tax Collected,Transactions",
|
|
58
|
+
...stateSummary.map(s => `${s.state},${fmt(s.taxable)},${(s.rate * 100).toFixed(2)}%,${fmt(s.tax_collected)},${s.transactions}`),
|
|
59
|
+
"",
|
|
60
|
+
"TRANSACTION DETAIL",
|
|
61
|
+
"Date,Description,Amount,Taxable,Tax Rate,Tax Amount,State",
|
|
62
|
+
...transactions.map(t => {
|
|
63
|
+
const tax = (t.taxable !== false && t.tax_rate > 0) ? round(t.amount * t.tax_rate) : 0;
|
|
64
|
+
return `${t.date},${esc(t.description)},${fmt(t.amount)},${t.taxable !== false && t.tax_rate > 0 ? "Yes" : "No"},${(t.tax_rate * 100).toFixed(2)}%,${fmt(tax)},${t.state || ""}`;
|
|
65
|
+
}),
|
|
66
|
+
"",
|
|
67
|
+
"TAX LIABILITY",
|
|
68
|
+
`Total Tax Owed,${fmt(totalTaxCollected)}`,
|
|
69
|
+
`Filing Deadline,${getNextQuarterEnd()}`,
|
|
70
|
+
];
|
|
71
|
+
return {
|
|
72
|
+
text: `Sales Tax: ${fmt(totalTaxCollected)} collected on ${fmt(totalTaxable)} taxable sales (${stateSummary.length} jurisdictions)`,
|
|
73
|
+
output_csv: csv.join("\n"),
|
|
74
|
+
summary: { total_sales: totalSales, total_taxable: totalTaxable, total_exempt: totalExempt, total_tax: totalTaxCollected, jurisdictions: stateSummary.length },
|
|
75
|
+
by_state: stateSummary,
|
|
76
|
+
duration_ms: Date.now() - start,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function getNextQuarterEnd() {
|
|
80
|
+
const now = new Date();
|
|
81
|
+
const m = now.getMonth();
|
|
82
|
+
const q = m < 3 ? 0 : m < 6 ? 1 : m < 9 ? 2 : 3;
|
|
83
|
+
const deadlines = ["04-30", "07-31", "10-31", "01-31"];
|
|
84
|
+
const year = q === 3 ? now.getFullYear() + 1 : now.getFullYear();
|
|
85
|
+
return `${year}-${deadlines[q]}`;
|
|
86
|
+
}
|
|
87
|
+
function round(n) { return Math.round(n * 100) / 100; }
|
|
88
|
+
function fmt(n) { return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }
|
|
89
|
+
function esc(v) { return v.includes(",") ? `"${v}"` : v; }
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleVarianceAnalysis = handleVarianceAnalysis;
|
|
4
|
+
async function handleVarianceAnalysis(task) {
|
|
5
|
+
const start = Date.now();
|
|
6
|
+
const input = task.input_payload;
|
|
7
|
+
const period = input.period || "Current Period";
|
|
8
|
+
const threshold = input.threshold_pct || 10;
|
|
9
|
+
const lines = input.lines || [
|
|
10
|
+
{ item: "Product Revenue", budget: 120000, actual: 115000, category: "Revenue" },
|
|
11
|
+
{ item: "Service Revenue", budget: 80000, actual: 92000, category: "Revenue" },
|
|
12
|
+
{ item: "Cost of Goods Sold", budget: 48000, actual: 52000, category: "COGS" },
|
|
13
|
+
{ item: "Salaries", budget: 70000, actual: 72800, category: "OpEx" },
|
|
14
|
+
{ item: "Marketing", budget: 15000, actual: 22000, category: "OpEx" },
|
|
15
|
+
{ item: "Rent", budget: 8500, actual: 8500, category: "OpEx" },
|
|
16
|
+
{ item: "Technology", budget: 10000, actual: 14200, category: "OpEx" },
|
|
17
|
+
{ item: "Travel", budget: 5000, actual: 8900, category: "OpEx" },
|
|
18
|
+
{ item: "Office Supplies", budget: 2000, actual: 1650, category: "OpEx" },
|
|
19
|
+
{ item: "Insurance", budget: 3200, actual: 3200, category: "OpEx" },
|
|
20
|
+
];
|
|
21
|
+
const analyzed = lines.map(l => {
|
|
22
|
+
const variance = round(l.actual - l.budget);
|
|
23
|
+
const variancePct = l.budget !== 0 ? round((variance / Math.abs(l.budget)) * 100) : 0;
|
|
24
|
+
const favorable = l.category === "Revenue" ? variance >= 0 : variance <= 0;
|
|
25
|
+
const significant = Math.abs(variancePct) >= threshold;
|
|
26
|
+
return { ...l, variance, variance_pct: variancePct, favorable, significant };
|
|
27
|
+
});
|
|
28
|
+
const significant = analyzed.filter(l => l.significant);
|
|
29
|
+
const favorableItems = analyzed.filter(l => l.favorable && l.variance !== 0);
|
|
30
|
+
const unfavorableItems = analyzed.filter(l => !l.favorable && l.variance !== 0);
|
|
31
|
+
const totalBudget = round(lines.reduce((s, l) => s + l.budget, 0));
|
|
32
|
+
const totalActual = round(lines.reduce((s, l) => s + l.actual, 0));
|
|
33
|
+
const totalVariance = round(totalActual - totalBudget);
|
|
34
|
+
const csv = [
|
|
35
|
+
"VARIANCE ANALYSIS REPORT",
|
|
36
|
+
`Period: ${period}`,
|
|
37
|
+
`Generated: ${new Date().toISOString().split("T")[0]}`,
|
|
38
|
+
`Significance Threshold: ${threshold}%`,
|
|
39
|
+
"",
|
|
40
|
+
"DETAILED ANALYSIS",
|
|
41
|
+
"Item,Category,Budget,Actual,Variance ($),Variance (%),Favorable?,Significant?",
|
|
42
|
+
...analyzed.map(l => `${esc(l.item)},${l.category || ""},${fmt(l.budget)},${fmt(l.actual)},${l.variance >= 0 ? "+" : ""}${fmt(l.variance)},${l.variance_pct >= 0 ? "+" : ""}${l.variance_pct}%,${l.favorable ? "Yes" : "No"},${l.significant ? "YES" : "No"}`),
|
|
43
|
+
"",
|
|
44
|
+
"SIGNIFICANT VARIANCES (>${threshold}%)",
|
|
45
|
+
"Item,Variance ($),Variance (%),Impact",
|
|
46
|
+
...significant.sort((a, b) => Math.abs(b.variance_pct) - Math.abs(a.variance_pct)).map(l => `${esc(l.item)},${l.variance >= 0 ? "+" : ""}${fmt(l.variance)},${l.variance_pct >= 0 ? "+" : ""}${l.variance_pct}%,${l.favorable ? "Favorable" : "Unfavorable"}`),
|
|
47
|
+
"",
|
|
48
|
+
"FAVORABLE VARIANCES",
|
|
49
|
+
"Item,Amount Saved/Gained",
|
|
50
|
+
...favorableItems.map(l => `${esc(l.item)},${fmt(Math.abs(l.variance))}`),
|
|
51
|
+
"",
|
|
52
|
+
"UNFAVORABLE VARIANCES",
|
|
53
|
+
"Item,Amount Over/Under",
|
|
54
|
+
...unfavorableItems.map(l => `${esc(l.item)},${fmt(Math.abs(l.variance))}`),
|
|
55
|
+
"",
|
|
56
|
+
"SUMMARY",
|
|
57
|
+
`Total Budget,${fmt(totalBudget)}`,
|
|
58
|
+
`Total Actual,${fmt(totalActual)}`,
|
|
59
|
+
`Net Variance,${totalVariance >= 0 ? "+" : ""}${fmt(totalVariance)}`,
|
|
60
|
+
`Significant Variances,${significant.length} of ${analyzed.length} items`,
|
|
61
|
+
`Favorable Items,${favorableItems.length}`,
|
|
62
|
+
`Unfavorable Items,${unfavorableItems.length}`,
|
|
63
|
+
];
|
|
64
|
+
return {
|
|
65
|
+
text: `Variance Analysis: ${significant.length} significant variances (>${threshold}%) | Net: ${totalVariance >= 0 ? "+" : ""}${fmt(totalVariance)}`,
|
|
66
|
+
output_csv: csv.join("\n"),
|
|
67
|
+
summary: { total_budget: totalBudget, total_actual: totalActual, total_variance: totalVariance, significant_count: significant.length, favorable_count: favorableItems.length, unfavorable_count: unfavorableItems.length },
|
|
68
|
+
duration_ms: Date.now() - start,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function round(n) { return Math.round(n * 100) / 100; }
|
|
72
|
+
function fmt(n) { return "$" + Math.abs(n).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }
|
|
73
|
+
function esc(v) { return v.includes(",") ? `"${v}"` : v; }
|