Functional Weave
Code in Python

education.fee-instalments@1.0.0

vectors.json

14,820 bytes · view raw

[
 {
  "name": "\u00a39,535 in equal thirds: the odd penny on the first instalment",
  "args": [
   {
    "minor": 953500,
    "currency": "GBP"
   },
   [
    1,
    1,
    1
   ],
   [
    "2025-10-01",
    "2026-01-15",
    "2026-04-20"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-10-01",
    "amount": {
     "minor": 317834,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 317834,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 635666,
     "currency": "GBP"
    }
   },
   {
    "number": 2,
    "dueDate": "2026-01-15",
    "amount": {
     "minor": 317833,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 635667,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 317833,
     "currency": "GBP"
    }
   },
   {
    "number": 3,
    "dueDate": "2026-04-20",
    "amount": {
     "minor": 317833,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 953500,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   }
  ]
 },
 {
  "name": "\u00a39,535 split 25/25/50 across three terms",
  "args": [
   {
    "minor": 953500,
    "currency": "GBP"
   },
   [
    25,
    25,
    50
   ],
   [
    "2025-10-01",
    "2026-01-15",
    "2026-04-20"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-10-01",
    "amount": {
     "minor": 238375,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 238375,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 715125,
     "currency": "GBP"
    }
   },
   {
    "number": 2,
    "dueDate": "2026-01-15",
    "amount": {
     "minor": 238375,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 476750,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 476750,
     "currency": "GBP"
    }
   },
   {
    "number": 3,
    "dueDate": "2026-04-20",
    "amount": {
     "minor": 476750,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 953500,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   }
  ]
 },
 {
  "name": "\u00a31,000.01 split 25/25/50: the odd penny goes to the 50% share, the largest remainder, not the first",
  "args": [
   {
    "minor": 100001,
    "currency": "GBP"
   },
   [
    25,
    25,
    50
   ],
   [
    "2025-10-01",
    "2026-01-15",
    "2026-04-20"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-10-01",
    "amount": {
     "minor": 25000,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 25000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 75001,
     "currency": "GBP"
    }
   },
   {
    "number": 2,
    "dueDate": "2026-01-15",
    "amount": {
     "minor": 25000,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 50000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 50001,
     "currency": "GBP"
    }
   },
   {
    "number": 3,
    "dueDate": "2026-04-20",
    "amount": {
     "minor": 50001,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 100001,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   }
  ]
 },
 {
  "name": "shares in basis points give the same schedule",
  "args": [
   {
    "minor": 100001,
    "currency": "GBP"
   },
   [
    2500,
    2500,
    5000
   ],
   [
    "2025-10-01",
    "2026-01-15",
    "2026-04-20"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-10-01",
    "amount": {
     "minor": 25000,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 25000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 75001,
     "currency": "GBP"
    }
   },
   {
    "number": 2,
    "dueDate": "2026-01-15",
    "amount": {
     "minor": 25000,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 50000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 50001,
     "currency": "GBP"
    }
   },
   {
    "number": 3,
    "dueDate": "2026-04-20",
    "amount": {
     "minor": 50001,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 100001,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   }
  ]
 },
 {
  "name": "\u00a3100 in thirds: 33.34, 33.33, 33.33",
  "args": [
   {
    "minor": 10000,
    "currency": "GBP"
   },
   [
    1,
    1,
    1
   ],
   [
    "2025-10-01",
    "2026-01-15",
    "2026-04-20"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-10-01",
    "amount": {
     "minor": 3334,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 3334,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 6666,
     "currency": "GBP"
    }
   },
   {
    "number": 2,
    "dueDate": "2026-01-15",
    "amount": {
     "minor": 3333,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 6667,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 3333,
     "currency": "GBP"
    }
   },
   {
    "number": 3,
    "dueDate": "2026-04-20",
    "amount": {
     "minor": 3333,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 10000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   }
  ]
 },
 {
  "name": "\u00a3100.01 in thirds: the two leftover pennies to the first two",
  "args": [
   {
    "minor": 10001,
    "currency": "GBP"
   },
   [
    1,
    1,
    1
   ],
   [
    "2025-10-01",
    "2026-01-15",
    "2026-04-20"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-10-01",
    "amount": {
     "minor": 3334,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 3334,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 6667,
     "currency": "GBP"
    }
   },
   {
    "number": 2,
    "dueDate": "2026-01-15",
    "amount": {
     "minor": 3334,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 6668,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 3333,
     "currency": "GBP"
    }
   },
   {
    "number": 3,
    "dueDate": "2026-04-20",
    "amount": {
     "minor": 3333,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 10001,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   }
  ]
 },
 {
  "name": "a single payment in full",
  "args": [
   {
    "minor": 925000,
    "currency": "GBP"
   },
   [
    1
   ],
   [
    "2025-09-22"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-09-22",
    "amount": {
     "minor": 925000,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 925000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   }
  ]
 },
 {
  "name": "a zero share collects nothing",
  "args": [
   {
    "minor": 900000,
    "currency": "GBP"
   },
   [
    0,
    1,
    1
   ],
   [
    "2025-10-01",
    "2026-01-15",
    "2026-04-20"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-10-01",
    "amount": {
     "minor": 0,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 0,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 900000,
     "currency": "GBP"
    }
   },
   {
    "number": 2,
    "dueDate": "2026-01-15",
    "amount": {
     "minor": 450000,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 450000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 450000,
     "currency": "GBP"
    }
   },
   {
    "number": 3,
    "dueDate": "2026-04-20",
    "amount": {
     "minor": 450000,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 900000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   }
  ]
 },
 {
  "name": "a zero fee is a schedule of zeros",
  "args": [
   {
    "minor": 0,
    "currency": "GBP"
   },
   [
    1,
    1
   ],
   [
    "2025-10-01",
    "2026-01-15"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-10-01",
    "amount": {
     "minor": 0,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 0,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   },
   {
    "number": 2,
    "dueDate": "2026-01-15",
    "amount": {
     "minor": 0,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 0,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   }
  ]
 },
 {
  "name": "euros keep their currency",
  "args": [
   {
    "minor": 1000,
    "currency": "EUR"
   },
   [
    1,
    1,
    1
   ],
   [
    "2025-10-01",
    "2026-01-15",
    "2026-04-20"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-10-01",
    "amount": {
     "minor": 334,
     "currency": "EUR"
    },
    "cumulative": {
     "minor": 334,
     "currency": "EUR"
    },
    "remaining": {
     "minor": 666,
     "currency": "EUR"
    }
   },
   {
    "number": 2,
    "dueDate": "2026-01-15",
    "amount": {
     "minor": 333,
     "currency": "EUR"
    },
    "cumulative": {
     "minor": 667,
     "currency": "EUR"
    },
    "remaining": {
     "minor": 333,
     "currency": "EUR"
    }
   },
   {
    "number": 3,
    "dueDate": "2026-04-20",
    "amount": {
     "minor": 333,
     "currency": "EUR"
    },
    "cumulative": {
     "minor": 1000,
     "currency": "EUR"
    },
    "remaining": {
     "minor": 0,
     "currency": "EUR"
    }
   }
  ]
 },
 {
  "name": "ten monthly payments of \u00a3925",
  "args": [
   {
    "minor": 925000,
    "currency": "GBP"
   },
   [
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1,
    1
   ],
   [
    "2025-10-01",
    "2025-11-01",
    "2025-12-01",
    "2026-01-01",
    "2026-02-01",
    "2026-03-01",
    "2026-04-01",
    "2026-05-01",
    "2026-06-01",
    "2026-07-01"
   ]
  ],
  "expect": [
   {
    "number": 1,
    "dueDate": "2025-10-01",
    "amount": {
     "minor": 92500,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 92500,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 832500,
     "currency": "GBP"
    }
   },
   {
    "number": 2,
    "dueDate": "2025-11-01",
    "amount": {
     "minor": 92500,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 185000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 740000,
     "currency": "GBP"
    }
   },
   {
    "number": 3,
    "dueDate": "2025-12-01",
    "amount": {
     "minor": 92500,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 277500,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 647500,
     "currency": "GBP"
    }
   },
   {
    "number": 4,
    "dueDate": "2026-01-01",
    "amount": {
     "minor": 92500,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 370000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 555000,
     "currency": "GBP"
    }
   },
   {
    "number": 5,
    "dueDate": "2026-02-01",
    "amount": {
     "minor": 92500,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 462500,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 462500,
     "currency": "GBP"
    }
   },
   {
    "number": 6,
    "dueDate": "2026-03-01",
    "amount": {
     "minor": 92500,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 555000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 370000,
     "currency": "GBP"
    }
   },
   {
    "number": 7,
    "dueDate": "2026-04-01",
    "amount": {
     "minor": 92500,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 647500,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 277500,
     "currency": "GBP"
    }
   },
   {
    "number": 8,
    "dueDate": "2026-05-01",
    "amount": {
     "minor": 92500,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 740000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 185000,
     "currency": "GBP"
    }
   },
   {
    "number": 9,
    "dueDate": "2026-06-01",
    "amount": {
     "minor": 92500,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 832500,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 92500,
     "currency": "GBP"
    }
   },
   {
    "number": 10,
    "dueDate": "2026-07-01",
    "amount": {
     "minor": 92500,
     "currency": "GBP"
    },
    "cumulative": {
     "minor": 925000,
     "currency": "GBP"
    },
    "remaining": {
     "minor": 0,
     "currency": "GBP"
    }
   }
  ]
 },
 {
  "name": "a negative total is an error",
  "args": [
   {
    "minor": -100,
    "currency": "GBP"
   },
   [
    1,
    1
   ],
   [
    "2025-10-01",
    "2026-01-15"
   ]
  ],
  "expectError": "total must not be negative, received -100"
 },
 {
  "name": "more shares than dates is an error",
  "args": [
   {
    "minor": 100,
    "currency": "GBP"
   },
   [
    1,
    1,
    1
   ],
   [
    "2025-10-01",
    "2026-01-15"
   ]
  ],
  "expectError": "shares and dueDates must be the same length, received 3 and 2"
 },
 {
  "name": "no instalments is an error",
  "args": [
   {
    "minor": 100,
    "currency": "GBP"
   },
   [],
   []
  ],
  "expectError": "shares must name at least one instalment"
 },
 {
  "name": "a negative share is an error",
  "args": [
   {
    "minor": 100,
    "currency": "GBP"
   },
   [
    1,
    -1
   ],
   [
    "2025-10-01",
    "2026-01-15"
   ]
  ],
  "expectError": "each share must be a whole number of 0 or more, received -1"
 },
 {
  "name": "a fractional share is an error",
  "args": [
   {
    "minor": 100,
    "currency": "GBP"
   },
   [
    1,
    1.5
   ],
   [
    "2025-10-01",
    "2026-01-15"
   ]
  ],
  "expectError": "each share must be a whole number of 0 or more, received 1.5"
 },
 {
  "name": "all-zero shares are an error",
  "args": [
   {
    "minor": 100,
    "currency": "GBP"
   },
   [
    0,
    0
   ],
   [
    "2025-10-01",
    "2026-01-15"
   ]
  ],
  "expectError": "shares must not all be zero"
 },
 {
  "name": "dates out of order are an error",
  "args": [
   {
    "minor": 100,
    "currency": "GBP"
   },
   [
    1,
    1
   ],
   [
    "2026-01-15",
    "2025-10-01"
   ]
  ],
  "expectError": "dueDates must be in strictly ascending order: 2025-10-01 follows 2026-01-15"
 },
 {
  "name": "two instalments on one day are an error",
  "args": [
   {
    "minor": 100,
    "currency": "GBP"
   },
   [
    1,
    1
   ],
   [
    "2025-10-01",
    "2025-10-01"
   ]
  ],
  "expectError": "dueDates must be in strictly ascending order"
 },
 {
  "name": "an impossible due date is an error",
  "args": [
   {
    "minor": 100,
    "currency": "GBP"
   },
   [
    1
   ],
   [
    "2026-02-30"
   ]
  ],
  "expectError": "\"2026-02-30\" is not a real calendar date"
 }
]