Files
dashback/src/tools/net-gross-calc.js
2026-10-11 13:06:38 +02:00

190 lines
7.0 KiB
JavaScript

import { defineTool, ToolError } from 'libllmorbiter';
import { TOOL_DESCRIPTIONS, TOOL_PARAMETER_DESCRIPTIONS } from './prompts.js';
const DEFAULT_TAX_RATE = 19;
const DEFAULT_PRECISION = 6;
/**
* Calculates net-to-gross or gross-to-net for a single price.
*
* @param {object} params
* @param {number} [params.net]
* @param {number} [params.gross]
* @param {number} [params.amount]
* @param {'net'|'gross'} [params.from]
* @param {number} [params.taxRate=19]
* @param {number} [params.precision=6]
* @returns {object}
*/
export function calculatePrice({ net, gross, amount, from, taxRate = DEFAULT_TAX_RATE, precision = DEFAULT_PRECISION }) {
const rate = taxRate != null ? Number(taxRate) : DEFAULT_TAX_RATE;
if (!Number.isFinite(rate) || rate < 0) {
throw new ToolError(`Invalid taxRate: ${taxRate}. Must be a non-negative number.`);
}
const prec = precision != null ? Math.min(8, Math.max(2, Number(precision) || DEFAULT_PRECISION)) : DEFAULT_PRECISION;
const factor = 1 + rate / 100;
let inputType = null;
let inputValue = null;
if (net != null && gross == null) {
inputType = 'net';
inputValue = Number(net);
} else if (gross != null && net == null) {
inputType = 'gross';
inputValue = Number(gross);
} else if (amount != null) {
inputValue = Number(amount);
inputType = from === 'net' ? 'net' : 'gross';
} else if (net != null && gross != null) {
// Both provided: validate consistency
const n = Number(net);
const g = Number(gross);
const calculatedGross = +(n * factor).toFixed(2);
const effectiveRate = n > 0 ? +(((g - n) / n) * 100).toFixed(2) : 0;
return {
direction: 'verify',
net: +n.toFixed(2),
gross: +g.toFixed(2),
taxRate: rate,
effectiveTaxRate: effectiveRate,
isConsistent: Math.abs(calculatedGross - g) <= 0.02,
taxAmount: +(g - n).toFixed(2),
};
} else {
throw new ToolError('net_gross_calc requires "net", "gross", or "amount".');
}
if (!Number.isFinite(inputValue) || inputValue < 0) {
throw new ToolError(`Invalid price amount: ${inputValue}. Must be a non-negative number.`);
}
if (inputType === 'gross') {
const grossVal = +inputValue.toFixed(2);
const exactNet = inputValue / factor;
const netRounded = +exactNet.toFixed(2);
const netPrecision = +exactNet.toFixed(prec);
const taxAmount = +(grossVal - netRounded).toFixed(2);
return {
direction: 'gross_to_net',
gross: grossVal,
net: netRounded,
netPrecision,
netPrecise: exactNet,
taxRate: rate,
taxAmount,
apiNetValue: netPrecision,
formatted: {
gross: `${grossVal.toFixed(2)} €`,
net: `${netRounded.toFixed(2)} €`,
tax: `${taxAmount.toFixed(2)} €`,
},
};
} else {
const netVal = +inputValue.toFixed(2);
const exactGross = inputValue * factor;
const grossRounded = +exactGross.toFixed(2);
const taxAmount = +(grossRounded - netVal).toFixed(2);
return {
direction: 'net_to_gross',
net: netVal,
gross: grossRounded,
grossPrecise: exactGross,
taxRate: rate,
taxAmount,
formatted: {
net: `${netVal.toFixed(2)} €`,
gross: `${grossRounded.toFixed(2)} €`,
tax: `${taxAmount.toFixed(2)} €`,
},
};
}
}
/**
* Creates the net_gross_calc tool.
*/
export function createNetGrossCalcTool() {
return defineTool({
name: 'net_gross_calc',
description: TOOL_DESCRIPTIONS.net_gross_calc,
strict: false,
parameters: {
type: 'object',
additionalProperties: false,
properties: {
note: {
type: 'string',
description: TOOL_PARAMETER_DESCRIPTIONS.net_gross_calc.note,
},
gross: {
type: ['number', 'null'],
description: TOOL_PARAMETER_DESCRIPTIONS.net_gross_calc.gross,
},
net: {
type: ['number', 'null'],
description: TOOL_PARAMETER_DESCRIPTIONS.net_gross_calc.net,
},
taxRate: {
type: ['number', 'null'],
description: TOOL_PARAMETER_DESCRIPTIONS.net_gross_calc.taxRate,
},
amount: {
type: ['number', 'null'],
description: TOOL_PARAMETER_DESCRIPTIONS.net_gross_calc.amount,
},
from: {
type: ['string', 'null'],
enum: ['gross', 'net', null],
description: TOOL_PARAMETER_DESCRIPTIONS.net_gross_calc.from,
},
precision: {
type: ['number', 'null'],
description: TOOL_PARAMETER_DESCRIPTIONS.net_gross_calc.precision,
},
items: {
type: ['array', 'null'],
description: TOOL_PARAMETER_DESCRIPTIONS.net_gross_calc.items,
items: {
type: 'object',
additionalProperties: false,
properties: {
gross: { type: ['number', 'null'] },
net: { type: ['number', 'null'] },
amount: { type: ['number', 'null'] },
from: { type: ['string', 'null'], enum: ['gross', 'net', null] },
taxRate: { type: ['number', 'null'] },
},
},
},
},
},
invoke: async ({ note, gross, net, amount, from, taxRate = DEFAULT_TAX_RATE, precision = DEFAULT_PRECISION, items } = {}) => {
if (Array.isArray(items) && items.length > 0) {
const results = items.map((item, idx) => {
const itemRate = item.taxRate != null ? item.taxRate : taxRate;
try {
return calculatePrice({
net: item.net,
gross: item.gross,
amount: item.amount,
from: item.from || from,
taxRate: itemRate,
precision,
});
} catch (err) {
return { itemIndex: idx, error: err.message };
}
});
return JSON.stringify({ count: results.length, results }, null, 2);
}
const result = calculatePrice({ net, gross, amount, from, taxRate, precision });
return JSON.stringify(result, null, 2);
},
});
}