54 lines
1.4 KiB
JavaScript
54 lines
1.4 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Computes a suggested expected amount for a bill based on the rolling median
|
|
* of the last 6 months of actual data. Prefers monthly_bill_state.actual_amount
|
|
* (user-corrected values) over raw payment sums.
|
|
*/
|
|
function computeAmountSuggestion(db, billId, year, month) {
|
|
const amounts = [];
|
|
let y = year;
|
|
let m = month;
|
|
|
|
for (let i = 0; i < 6; i++) {
|
|
m -= 1;
|
|
if (m === 0) { m = 12; y -= 1; }
|
|
|
|
const mbs = db.prepare(
|
|
'SELECT actual_amount FROM monthly_bill_state WHERE bill_id = ? AND year = ? AND month = ?'
|
|
).get(billId, y, m);
|
|
|
|
if (mbs?.actual_amount != null) {
|
|
amounts.push(mbs.actual_amount);
|
|
continue;
|
|
}
|
|
|
|
const result = db.prepare(`
|
|
SELECT COALESCE(SUM(amount), 0) AS total
|
|
FROM payments
|
|
WHERE bill_id = ?
|
|
AND deleted_at IS NULL
|
|
AND strftime('%Y', paid_date) = ?
|
|
AND strftime('%m', paid_date) = ?
|
|
`).get(billId, String(y), String(m).padStart(2, '0'));
|
|
|
|
if (result.total > 0) amounts.push(result.total);
|
|
}
|
|
|
|
if (amounts.length === 0) return null;
|
|
|
|
const sorted = [...amounts].sort((a, b) => a - b);
|
|
const mid = Math.floor(sorted.length / 2);
|
|
const median = sorted.length % 2 === 0
|
|
? (sorted[mid - 1] + sorted[mid]) / 2
|
|
: sorted[mid];
|
|
|
|
return {
|
|
suggestion: Math.round(median * 100) / 100,
|
|
months_used: amounts.length,
|
|
confidence: amounts.length >= 3 ? 'high' : 'low',
|
|
};
|
|
}
|
|
|
|
module.exports = { computeAmountSuggestion };
|