app/composables/useCushionForecast.ts

92 lines
No EOL
2.8 KiB
TypeScript

import { monthlyPayroll } from '~/types/members'
export function useCushionForecast() {
const cashStore = useCashStore()
const membersStore = useMembersStore()
const policiesStore = usePoliciesStore()
const budgetStore = useBudgetStore()
// Savings progress calculation
const savingsProgress = computed(() => {
const current = cashStore.currentSavings || 0
const targetMonths = policiesStore.savingsTargetMonths || 3
const monthlyBurn = getMonthlyBurn()
const target = targetMonths * monthlyBurn
return {
current,
target,
targetMonths,
progressPct: target > 0 ? Math.min(100, (current / target) * 100) : 0,
gap: Math.max(0, target - current),
status: getProgressStatus(current, target)
}
})
// 13-week cushion breach forecast
const cushionForecast = computed(() => {
const minCushion = policiesStore.minCashCushionAmount || 5000
const currentBalance = cashStore.currentCash || 0
const monthlyBurn = getMonthlyBurn()
const weeklyBurn = monthlyBurn / 4.33 // Convert monthly to weekly
const weeks = []
let runningBalance = currentBalance
for (let week = 1; week <= 13; week++) {
// Simple projection: subtract weekly burn
runningBalance -= weeklyBurn
const breachesCushion = runningBalance < minCushion
weeks.push({
week,
balance: runningBalance,
breachesCushion,
cushionAmount: minCushion
})
}
const firstBreachWeek = weeks.find(w => w.breachesCushion)?.week
const breachesWithin13Weeks = Boolean(firstBreachWeek)
return {
weeks,
firstBreachWeek,
breachesWithin13Weeks,
minCushion,
weeksUntilBreach: firstBreachWeek || null
}
})
function getMonthlyBurn() {
const operatingMode = policiesStore.operatingMode || 'minimum'
const payrollCost = monthlyPayroll(membersStore.members, operatingMode)
const oncostPct = policiesStore.payrollOncostPct || 0
const totalPayroll = payrollCost * (1 + oncostPct / 100)
const overheadCost = budgetStore.overheadCosts.reduce((sum, cost) => sum + (cost.amount || 0), 0)
return totalPayroll + overheadCost
}
function getProgressStatus(current: number, target: number): 'green' | 'yellow' | 'red' {
if (target === 0) return 'green'
const pct = (current / target) * 100
if (pct >= 80) return 'green'
if (pct >= 50) return 'yellow'
return 'red'
}
// Alert conditions (matching CLAUDE.md)
const alerts = computed(() => ({
savingsBelowTarget: savingsProgress.value.current < savingsProgress.value.target,
cushionBreach: cushionForecast.value.breachesWithin13Weeks,
}))
return {
savingsProgress,
cushionForecast,
alerts,
getMonthlyBurn
}
}