from .dates_days_between import days_between from .insurance_mid_term_adjustment_types import MidTermAdjustment from .math_round_div import RoundingMode, round_div from .money_amount import Money, assert_same_currency, money def mid_term_adjustment( old_premium: Money, new_premium: Money, inception_date: str, expiry_date: str, change_date: str, admin_fee: Money, mode: RoundingMode, ) -> MidTermAdjustment: """The additional or return premium for a change part-way through a policy term. The difference between the two full-term premiums is pro-rated over the days left, with one rounding. Pro-rating the old and new premiums separately and subtracting rounds twice and can be a penny out. """ assert_same_currency(old_premium, new_premium) assert_same_currency(old_premium, admin_fee) if old_premium.minor < 0 or new_premium.minor < 0: raise ValueError("premiums must not be negative") if admin_fee.minor < 0: raise ValueError("adminFee must not be negative, received %d" % (admin_fee.minor,)) total_days = days_between(inception_date, expiry_date) if total_days <= 0: raise ValueError( "expiryDate must be after inceptionDate, received %s to %s" % (inception_date, expiry_date) ) remaining_days = days_between(change_date, expiry_date) if remaining_days < 0 or remaining_days > total_days: raise ValueError("changeDate must be from inceptionDate to expiryDate, received %s" % (change_date,)) change = round_div((new_premium.minor - old_premium.minor) * remaining_days, total_days, mode) currency = old_premium.currency return MidTermAdjustment( total_days=total_days, remaining_days=remaining_days, premium_change=money(change, currency), admin_fee=admin_fee, amount_due=money(change + admin_fee.minor, currency), kind="additional" if change > 0 else "return" if change < 0 else "none", )