5 - Add buffer multiplier support to Bucket model with effective capacity
This commit is contained in:
parent
b4903cf3cf
commit
772f4c1c5a
3 changed files with 125 additions and 2 deletions
|
|
@ -22,6 +22,7 @@
|
|||
* @property BucketAllocationTypeEnum $allocation_type
|
||||
* @property float $starting_amount
|
||||
* @property float $allocation_value
|
||||
* @property float $buffer_multiplier
|
||||
*
|
||||
* @method static BucketFactory factory()
|
||||
*/
|
||||
|
|
@ -38,6 +39,7 @@ class Bucket extends Model
|
|||
'sort_order',
|
||||
'allocation_type',
|
||||
'allocation_value',
|
||||
'buffer_multiplier',
|
||||
'starting_amount',
|
||||
];
|
||||
|
||||
|
|
@ -46,6 +48,7 @@ class Bucket extends Model
|
|||
'priority' => 'integer',
|
||||
'sort_order' => 'integer',
|
||||
'allocation_value' => 'decimal:2',
|
||||
'buffer_multiplier' => 'decimal:2',
|
||||
'starting_amount' => 'integer',
|
||||
'allocation_type' => BucketAllocationTypeEnum::class,
|
||||
];
|
||||
|
|
@ -99,6 +102,21 @@ public function getCurrentBalance(): int
|
|||
return $this->starting_amount + $totalDraws - $totalOutflows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the effective capacity including buffer.
|
||||
* Formula: allocation_value * (1 + buffer_multiplier)
|
||||
*/
|
||||
public function getEffectiveCapacity(): float
|
||||
{
|
||||
if ($this->allocation_type !== BucketAllocationTypeEnum::FIXED_LIMIT) {
|
||||
return PHP_FLOAT_MAX;
|
||||
}
|
||||
|
||||
$base = (float) ($this->allocation_value ?? 0);
|
||||
|
||||
return round($base * (1 + (float) $this->buffer_multiplier), 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the bucket can accept more money (for fixed_limit buckets).
|
||||
*/
|
||||
|
|
@ -108,7 +126,7 @@ public function hasAvailableSpace(): bool
|
|||
return true;
|
||||
}
|
||||
|
||||
return $this->getCurrentBalance() < $this->allocation_value;
|
||||
return $this->getCurrentBalance() < $this->getEffectiveCapacity();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -120,7 +138,7 @@ public function getAvailableSpace(): float
|
|||
return PHP_FLOAT_MAX;
|
||||
}
|
||||
|
||||
return max(0, $this->allocation_value - $this->getCurrentBalance());
|
||||
return max(0, $this->getEffectiveCapacity() - $this->getCurrentBalance());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ public function definition(): array
|
|||
'sort_order' => $this->faker->numberBetween(0, 10),
|
||||
'allocation_type' => $allocationType,
|
||||
'allocation_value' => $this->getAllocationValueForType($allocationType),
|
||||
'buffer_multiplier' => 0,
|
||||
'starting_amount' => $this->faker->numberBetween(0, 100000), // $0 to $1000 in cents
|
||||
];
|
||||
}
|
||||
|
|
@ -119,6 +120,16 @@ public function overflow(): Factory
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bucket with a buffer multiplier.
|
||||
*/
|
||||
public function withBuffer(float $multiplier): Factory
|
||||
{
|
||||
return $this->state([
|
||||
'buffer_multiplier' => $multiplier,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default buckets set (Monthly Expenses, Emergency Fund, Overflow).
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -79,4 +79,98 @@ public function test_current_balance_without_starting_amount_defaults_to_zero()
|
|||
// starting_amount (0) + draws (30000) - outflows (10000) = 20000
|
||||
$this->assertEquals(20000, $bucket->getCurrentBalance());
|
||||
}
|
||||
|
||||
public function test_effective_capacity_with_zero_buffer_equals_allocation_value(): void
|
||||
{
|
||||
$scenario = Scenario::factory()->create();
|
||||
$bucket = Bucket::factory()->fixedLimit(50000)->create([
|
||||
'scenario_id' => $scenario->id,
|
||||
'buffer_multiplier' => 0,
|
||||
]);
|
||||
|
||||
$this->assertEquals(50000.0, $bucket->getEffectiveCapacity());
|
||||
}
|
||||
|
||||
public function test_effective_capacity_with_full_buffer_doubles_capacity(): void
|
||||
{
|
||||
$scenario = Scenario::factory()->create();
|
||||
$bucket = Bucket::factory()->fixedLimit(50000)->create([
|
||||
'scenario_id' => $scenario->id,
|
||||
'buffer_multiplier' => 1.0,
|
||||
]);
|
||||
|
||||
$this->assertEquals(100000.0, $bucket->getEffectiveCapacity());
|
||||
}
|
||||
|
||||
public function test_effective_capacity_with_half_buffer(): void
|
||||
{
|
||||
$scenario = Scenario::factory()->create();
|
||||
$bucket = Bucket::factory()->fixedLimit(50000)->create([
|
||||
'scenario_id' => $scenario->id,
|
||||
'buffer_multiplier' => 0.5,
|
||||
]);
|
||||
|
||||
$this->assertEquals(75000.0, $bucket->getEffectiveCapacity());
|
||||
}
|
||||
|
||||
public function test_effective_capacity_for_percentage_returns_php_float_max(): void
|
||||
{
|
||||
$scenario = Scenario::factory()->create();
|
||||
$bucket = Bucket::factory()->percentage(25)->create([
|
||||
'scenario_id' => $scenario->id,
|
||||
'buffer_multiplier' => 1.0,
|
||||
]);
|
||||
|
||||
$this->assertEquals(PHP_FLOAT_MAX, $bucket->getEffectiveCapacity());
|
||||
}
|
||||
|
||||
public function test_effective_capacity_for_unlimited_returns_php_float_max(): void
|
||||
{
|
||||
$scenario = Scenario::factory()->create();
|
||||
$bucket = Bucket::factory()->unlimited()->create([
|
||||
'scenario_id' => $scenario->id,
|
||||
'buffer_multiplier' => 1.0,
|
||||
]);
|
||||
|
||||
$this->assertEquals(PHP_FLOAT_MAX, $bucket->getEffectiveCapacity());
|
||||
}
|
||||
|
||||
public function test_has_available_space_uses_effective_capacity(): void
|
||||
{
|
||||
$scenario = Scenario::factory()->create();
|
||||
$bucket = Bucket::factory()->fixedLimit(50000)->create([
|
||||
'scenario_id' => $scenario->id,
|
||||
'starting_amount' => 50000,
|
||||
'buffer_multiplier' => 1.0,
|
||||
]);
|
||||
|
||||
// Balance is 50000, effective capacity is 100000 — still has space
|
||||
$this->assertTrue($bucket->hasAvailableSpace());
|
||||
}
|
||||
|
||||
public function test_has_available_space_false_when_at_effective_capacity(): void
|
||||
{
|
||||
$scenario = Scenario::factory()->create();
|
||||
$bucket = Bucket::factory()->fixedLimit(50000)->create([
|
||||
'scenario_id' => $scenario->id,
|
||||
'starting_amount' => 100000,
|
||||
'buffer_multiplier' => 1.0,
|
||||
]);
|
||||
|
||||
// Balance is 100000, effective capacity is 100000 — no space
|
||||
$this->assertFalse($bucket->hasAvailableSpace());
|
||||
}
|
||||
|
||||
public function test_get_available_space_uses_effective_capacity(): void
|
||||
{
|
||||
$scenario = Scenario::factory()->create();
|
||||
$bucket = Bucket::factory()->fixedLimit(50000)->create([
|
||||
'scenario_id' => $scenario->id,
|
||||
'starting_amount' => 30000,
|
||||
'buffer_multiplier' => 1.0,
|
||||
]);
|
||||
|
||||
// Effective capacity 100000 - balance 30000 = 70000 available
|
||||
$this->assertEquals(70000.0, $bucket->getAvailableSpace());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue