Functional Weave
Code in TypeScript

insurance.mid-term-adjustment Unreviewed

Additional or return premium for a mid-term policy change, pro rata on the days left, plus an admin fee.

1.0.1 · published 2026-10-03 by charlie · Anterra

Pinned by 17 tests, run in TypeScript, Python and Rust.

Unreviewed. This capability’s implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified actuary has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.

Not professional advice. This capability calculates insurance figures from published rules. It is a software component for developers, not financial advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed in its README, and have an actuary review how you use it, before anyone relies on the output. Provided “as is” under its licence, without warranty.

What it does

The additional premium (AP) or return premium (RP) when a policy is changed part-way through its term: a driver added, a car swapped, cover reduced.

## How it is worked out

For example

  • midTermAdjustment(£500.00, £620.00, 2026-01-01, 2027-01-01, 2026-07-02, £25.00, half-up) → total days 365, remaining days 183, premium change £60.16, admin fee £25.00, amount due £85.16, kind additional adding a driver half way: 120.00 more a year for 183 of 365 days is 60.16, plus a 25.00 fee
  • midTermAdjustment(£500.00, £620.00, 2026-01-01, 2027-01-01, 2026-07-02, £0.00, half-up) → total days 365, remaining days 183, premium change £60.16, admin fee £0.00, amount due £60.16, kind additional one rounding of the difference, not two: pro-rating 500.00 and 620.00 separately would give 60.17
  • midTermAdjustment(£620.00, £500.00, 2026-01-01, 2027-01-01, 2026-07-02, £25.00, half-up) → total days 365, remaining days 183, premium change -£60.16, admin fee £25.00, amount due -£35.16, kind return removing the driver returns 60.16, less the 25.00 fee

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

export function midTermAdjustment(oldPremium: Money, newPremium: Money, inceptionDate: string, expiryDate: string, changeDate: string, adminFee: Money, mode: RoundingMode): MidTermAdjustment
oldPremiumMoneythe full-term premium before the change
newPremiumMoneythe full-term premium the policy would have cost with the change from inception
inceptionDatedatethe first day of the policy term
expiryDatedatethe day the term ends, exclusive: a year from 2026-01-01 ends 2027-01-01
changeDatedatethe first day of the changed cover, from inceptionDate to expiryDate
adminFeeMoneythe insurer's or broker's charge for making the change, 0 or more
modeRoundingModehow the pro-rata premium change rounds to a minor unit
returnsMidTermAdjustment

The types it declares, generated into your project

export type AdjustmentKind = "additional" | "return" | "none";

/** The premium change for the rest of the term, the fee and what is due. */
export interface MidTermAdjustment {
  readonly totalDays: number;
  /** days from changeDate to expiryDate */
  readonly remainingDays: number;
  /** the difference in premium for the remaining days; negative is a return premium */
  readonly premiumChange: Money;
  readonly adminFee: Money;
  /** premiumChange plus adminFee; negative is owed to the policyholder */
  readonly amountDue: Money;
  /** additional or return premium by the sign of premiumChange */
  readonly kind: AdjustmentKind;
}

Your code names it in one line, in the file that uses it

import { midTermAdjustment } from "#fune/insurance.mid-term-adjustment@^1";
impl/typescript.ts · 48 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { daysBetween } from "./dates_days_between.ts";  ← from dates.days-between ^1.0.0 · built alongside by fune
import { type RoundingMode, roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, assertSameCurrency, money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { type MidTermAdjustment } from "./insurance_mid_term_adjustment_types.ts";

/**
 * 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.
 */
export function midTermAdjustment(
  oldPremium: Money,
  newPremium: Money,
  inceptionDate: string,
  expiryDate: string,
  changeDate: string,
  adminFee: Money,
  mode: RoundingMode
): MidTermAdjustment {
  assertSameCurrency(oldPremium, newPremium);
  assertSameCurrency(oldPremium, adminFee);
  if (oldPremium.minor < 0 || newPremium.minor < 0) {
    throw new RangeError("premiums must not be negative");
  }
  if (adminFee.minor < 0) {
    throw new RangeError(`adminFee must not be negative, received ${adminFee.minor}`);
  }
  const totalDays = daysBetween(inceptionDate, expiryDate);
  if (totalDays <= 0) {
    throw new RangeError(`expiryDate must be after inceptionDate, received ${inceptionDate} to ${expiryDate}`);
  }
  const remainingDays = daysBetween(changeDate, expiryDate);
  if (remainingDays < 0 || remainingDays > totalDays) {
    throw new RangeError(`changeDate must be from inceptionDate to expiryDate, received ${changeDate}`);
  }
  const change = roundDiv((newPremium.minor - oldPremium.minor) * remainingDays, totalDays, mode);
  const currency = oldPremium.currency;
  return {
    totalDays,
    remainingDays,
    premiumChange: money(change, currency),
    adminFee,
    amountDue: money(change + adminFee.minor, currency),
    kind: change > 0 ? "additional" : change < 0 ? "return" : "none",
  };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 3 dependencies, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add insurance.mid-term-adjustment
Download for TypeScript insurance.mid-term-adjustment-1.0.1-typescript.fune · 18,511 bytes sha256 6e3f32017a598fec381786f5a466639655c3d7463fc97a5125b2ddb360bf115c

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./insurance.mid-term-adjustment-1.0.1-typescript.fune, or fetch it from a terminal with fune pull insurance.mid-term-adjustment@1.0.1:typescript.

The whole function, every language, is one file too: insurance.mid-term-adjustment-1.0.1.fune, 23,546 bytes, sha256 20a8cb56d87453621b8f8019e80a7a541baf3ddc581fffb67386f6c0b352b594. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

// fune: before insurance.mid-term-adjustment

after — your function gets the result and the arguments, and returns the final result.

// fune: after insurance.mid-term-adjustment

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

// fune: replace dates.days-between in insurance.mid-term-adjustment
// fune: replace math.round-div in insurance.mid-term-adjustment
// fune: replace money.amount in insurance.mid-term-adjustment

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show insurance.mid-term-adjustment --steps.

// fune: step insurance.mid-term-adjustment after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
adding a driver half way: 120.00 more a year for 183 of 365 days is 60.16, plus a 25.00 fee £500.00, £620.00, 2026-01-01, 2027-01-01, 2026-07-02, £25.00, half-up → total days 365, remaining days 183, premium change £60.16, admin fee £25.00, amount due £85.16, kind additional
one rounding of the difference, not two: pro-rating 500.00 and 620.00 separately would give 60.17 £500.00, £620.00, 2026-01-01, 2027-01-01, 2026-07-02, £0.00, half-up → total days 365, remaining days 183, premium change £60.16, admin fee £0.00, amount due £60.16, kind additional
removing the driver returns 60.16, less the 25.00 fee £620.00, £500.00, 2026-01-01, 2027-01-01, 2026-07-02, £25.00, half-up → total days 365, remaining days 183, premium change -£60.16, admin fee £25.00, amount due -£35.16, kind return
rounding up charges the part penny £500.00, £620.00, 2026-01-01, 2027-01-01, 2026-07-02, £0.00, up → total days 365, remaining days 183, premium change £60.17, admin fee £0.00, amount due £60.17, kind additional
a change from inception is the whole difference £500.00, £620.00, 2026-01-01, 2027-01-01, 2026-01-01, £0.00, half-up → total days 365, remaining days 365, premium change £120.00, admin fee £0.00, amount due £120.00, kind additional
a change on the expiry date costs nothing but the fee £500.00, £620.00, 2026-01-01, 2027-01-01, 2027-01-01, £25.00, half-up → total days 365, remaining days 0, premium change £0.00, admin fee £25.00, amount due £25.00, kind none
a change with no premium effect still carries its fee £500.00, £500.00, 2026-01-01, 2027-01-01, 2026-03-15, £15.00, half-up → total days 365, remaining days 292, premium change £0.00, admin fee £15.00, amount due £15.00, kind none
a leap-year term has 366 days: 366.00 more for the last 31 is 31.00 £0.00, £366.00, 2028-01-01, 2029-01-01, 2028-12-01, £0.00, half-up → total days 366, remaining days 31, premium change £31.00, admin fee £0.00, amount due £31.00, kind additional
exactly half a penny rounds up half-up £100.00, £100.01, 2026-01-01, 2026-01-05, 2026-01-03, £0.00, half-up → total days 4, remaining days 2, premium change £0.01, admin fee £0.00, amount due £0.01, kind additional
exactly half a penny rounds to even: nothing to charge £100.00, £100.01, 2026-01-01, 2026-01-05, 2026-01-03, £0.00, half-even → total days 4, remaining days 2, premium change £0.00, admin fee £0.00, amount due £0.00, kind none
Show the other 7 tests
CaseArgumentsExpected
a return premium smaller than the fee still leaves something to pay £500.00, £490.00, 2026-01-01, 2027-01-01, 2026-07-02, £25.00, half-up → total days 365, remaining days 183, premium change -£5.01, admin fee £25.00, amount due £19.99, kind return
a change before inception is refused £500.00, £620.00, 2026-01-01, 2027-01-01, 2025-12-31, £0.00, half-up → error: changeDate must be from inceptionDate to expiryDate
a change after expiry is refused £500.00, £620.00, 2026-01-01, 2027-01-01, 2027-01-02, £0.00, half-up → error: changeDate must be from inceptionDate to expiryDate
expiry must be after inception £500.00, £620.00, 2027-01-01, 2026-01-01, 2026-01-01, £0.00, half-up → error: expiryDate must be after inceptionDate
a negative fee is refused £500.00, £620.00, 2026-01-01, 2027-01-01, 2026-07-02, -£0.01, half-up → error: adminFee must not be negative
a negative premium is refused -£500.00, £620.00, 2026-01-01, 2027-01-01, 2026-07-02, £0.00, half-up → error: premiums must not be negative
premiums in two currencies are refused £500.00, €620.00, 2026-01-01, 2027-01-01, 2026-07-02, £0.00, half-up → error: currency mismatch

More from the author

Give it the full-term premium before the change and the full-term premium the policy would cost with the change (as your rating engine quotes it), the term dates and the date the change takes effect. The difference is pro-rated on the days left:

premiumChange = (newPremium - oldPremium) x remainingDays / totalDays

with one rounding, in the caller's mode. Pro-rating the old and the new premium separately and subtracting rounds twice and can be a penny out (the vectors have one such case: 60.16, not 60.17).

`expiryDate` is exclusive (a year from 2026-01-01 ends 2027-01-01), and the days left are `expiryDate - changeDate`, so a change on the inception date is the whole difference and a change on the expiry date is nothing. Leap years count their 366 days.

## The admin fee

`adminFee` is added to what is due whichever way the premium moves, which is how most UK insurers and brokers apply an MTA fee: on a return premium the fee is deducted from the refund, and if the fee is larger the policyholder still pays the difference (`amountDue` positive while `kind` is `return`). Pass zero when no fee is charged. The fee is kept separate from the premium change because it is usually not premium: IPT (`insurance.ipt`) applies to `premiumChange`, and to the fee only when the insurer charges it as part of the premium.

## Not covered here

Short-period loadings on MTAs, minimum additional premiums and "no refund below X" rules are insurer-specific terms of business; apply them to the result.

## Before you rely on this

**Not professional advice.** This capability calculates insurance figures from published rules. It is a software component for developers, not financial advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed above, and have an actuary review how you use it, before anyone relies on the output. Provided "as is" under its licence, without warranty.

**Unreviewed.** This capability's implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified actuary has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.

1.0.1 marks it unreviewed. The code and the tests are unchanged.

Files

PathBytes
README.md2,789
impl/python.py1,992
impl/rust.rs2,851
impl/typescript.ts1,921
vectors.json8,637