가장 큰 하루 1건만 제외하던 것을, 중앙값 3배(또는 10만원) 초과 '모든' 날 제외로 변경. 대출상환·다건 일회성이 일평균에 남아 남은 날에 곱해져 예상지출이 과다 추정되던 문제 해결. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -72,16 +72,27 @@ const expenseDelta = computed(() => {
|
||||
일별 지출을 받아 '평소 하루 지출'을 구하되, 일회성 큰 지출(노트북 등)이 있는 날은
|
||||
일평균을 왜곡하므로 제외한다. 추정 = 실제 지출(일회성 포함) + 남은 날 × 평소 하루 지출. */
|
||||
function robustDailyRate(days) {
|
||||
if (!days.length) return { rate: 0, dropped: 0 }
|
||||
if (!days.length) return { rate: 0, dropped: 0, droppedCount: 0 }
|
||||
const sorted = [...days].sort((a, b) => a - b)
|
||||
const mid = Math.floor(sorted.length / 2)
|
||||
const median = sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2
|
||||
const max = sorted[sorted.length - 1]
|
||||
// 최대 지출일이 중앙값의 3배(그리고 10만원)를 넘으면 일회성으로 보고 하루평균 산정에서 제외
|
||||
const dropOutlier = sorted.length >= 4 && max > Math.max(median * 3, 100000)
|
||||
const base = dropOutlier ? sorted.slice(0, -1) : sorted
|
||||
const rate = base.reduce((a, b) => a + b, 0) / base.length
|
||||
return { rate, dropped: dropOutlier ? max : 0 }
|
||||
// 비정기 큰 지출(대출상환·가전·여행 등)은 남은 날에 반복되지 않으므로,
|
||||
// 중앙값의 3배(또는 10만원)를 넘는 '모든' 날을 하루평균 산정에서 제외(가장 큰 1건만이 아님).
|
||||
const threshold = Math.max(median * 3, 100000)
|
||||
const canTrim = sorted.length >= 4
|
||||
const kept = []
|
||||
let dropped = 0
|
||||
let droppedCount = 0
|
||||
for (const d of sorted) {
|
||||
if (canTrim && d > threshold) {
|
||||
dropped += d
|
||||
droppedCount++
|
||||
} else {
|
||||
kept.push(d)
|
||||
}
|
||||
}
|
||||
const rate = kept.length ? kept.reduce((a, b) => a + b, 0) / kept.length : 0
|
||||
return { rate, dropped, droppedCount }
|
||||
}
|
||||
async function loadForecast() {
|
||||
forecast.value = null
|
||||
@@ -98,12 +109,12 @@ async function loadForecast() {
|
||||
;(daily || []).forEach((d) => (map[String(d.bucket)] = d.expense || 0))
|
||||
const days = []
|
||||
for (let d = 1; d <= elapsed; d++) days.push(map[String(d)] || 0)
|
||||
const { rate, dropped } = robustDailyRate(days)
|
||||
const { rate, dropped, droppedCount } = robustDailyRate(days)
|
||||
const remaining = totalDays - elapsed
|
||||
forecast.value = Math.round(monthExpense.value + rate * remaining)
|
||||
forecastNote.value =
|
||||
dropped > 0
|
||||
? `가장 큰 하루 지출(${won(dropped)})은 일회성으로 보고 남은 ${remaining}일 추정에서 제외`
|
||||
droppedCount > 0
|
||||
? `대출상환 등 비정기 큰 지출 ${droppedCount}건(합계 ${won(dropped)})은 남은 ${remaining}일 추정에서 제외`
|
||||
: `최근 하루 평균 지출로 남은 ${remaining}일을 더한 추정`
|
||||
} catch {
|
||||
forecast.value = null
|
||||
|
||||
Reference in New Issue
Block a user