85 Number expectedPaymentFactor;
90 .name =
"Zero periodic rate",
92 .paymentsRemaining = 4,
93 .expectedPaymentFactor =
Number{25, -2},
96 .name =
"One payment remaining",
97 .periodicRate =
Number{5, -2},
98 .paymentsRemaining = 1,
99 .expectedPaymentFactor =
Number{105, -2},
102 .name =
"Multiple payments remaining",
103 .periodicRate =
Number{5, -2},
104 .paymentsRemaining = 3,
105 .expectedPaymentFactor =
Number{3672085646312450436, -19},
108 .name =
"Zero payments remaining",
109 .periodicRate =
Number{5, -2},
110 .paymentsRemaining = 0,
111 .expectedPaymentFactor =
Number{0},
115 for (
auto const& tc : testCases)
117 testcase(
"computePaymentFactor: " + tc.name);
119 auto const computedPaymentFactor = computePaymentFactor(tc.periodicRate, tc.paymentsRemaining);
121 computedPaymentFactor == tc.expectedPaymentFactor,
122 "Payment factor mismatch: expected " +
to_string(tc.expectedPaymentFactor) +
", got " +
136 Number principalOutstanding;
139 Number expectedPeriodicPayment;
144 .name =
"Zero principal outstanding",
145 .principalOutstanding =
Number{0},
146 .periodicRate =
Number{5, -2},
147 .paymentsRemaining = 5,
148 .expectedPeriodicPayment =
Number{0},
151 .name =
"Zero payments remaining",
152 .principalOutstanding =
Number{1'000},
153 .periodicRate =
Number{5, -2},
154 .paymentsRemaining = 0,
155 .expectedPeriodicPayment =
Number{0},
158 .name =
"Zero periodic rate",
159 .principalOutstanding =
Number{1'000},
160 .periodicRate =
Number{0},
161 .paymentsRemaining = 4,
162 .expectedPeriodicPayment =
Number{250},
165 .name =
"Standard case",
166 .principalOutstanding =
Number{1'000},
168 .paymentsRemaining = 3,
169 .expectedPeriodicPayment =
Number{389569066396123265, -15},
173 for (
auto const& tc : testCases)
175 testcase(
"loanPeriodicPayment: " + tc.name);
177 auto const computedPeriodicPayment =
178 loanPeriodicPayment(tc.principalOutstanding, tc.periodicRate, tc.paymentsRemaining);
180 computedPeriodicPayment == tc.expectedPeriodicPayment,
181 "Periodic payment mismatch: expected " +
to_string(tc.expectedPeriodicPayment) +
", got " +
198 Number expectedPrincipalOutstanding;
203 .name =
"Zero periodic payment",
204 .periodicPayment =
Number{0},
205 .periodicRate =
Number{5, -2},
206 .paymentsRemaining = 5,
207 .expectedPrincipalOutstanding =
Number{0},
210 .name =
"Zero payments remaining",
211 .periodicPayment =
Number{1'000},
212 .periodicRate =
Number{5, -2},
213 .paymentsRemaining = 0,
214 .expectedPrincipalOutstanding =
Number{0},
217 .name =
"Zero periodic rate",
218 .periodicPayment =
Number{250},
219 .periodicRate =
Number{0},
220 .paymentsRemaining = 4,
221 .expectedPrincipalOutstanding =
Number{1'000},
224 .name =
"Standard case",
225 .periodicPayment =
Number{389569066396123265, -15},
227 .paymentsRemaining = 3,
228 .expectedPrincipalOutstanding =
Number{1'000},
232 for (
auto const& tc : testCases)
234 testcase(
"loanPrincipalFromPeriodicPayment: " + tc.name);
236 auto const computedPrincipalOutstanding =
237 loanPrincipalFromPeriodicPayment(tc.periodicPayment, tc.periodicRate, tc.paymentsRemaining);
239 computedPrincipalOutstanding == tc.expectedPrincipalOutstanding,
240 "Principal outstanding mismatch: expected " +
to_string(tc.expectedPrincipalOutstanding) +
", got " +
241 to_string(computedPrincipalOutstanding));
248 testcase(
"computeOverpaymentComponents");
252 Account const issuer{
"issuer"};
254 int32_t
const loanScale = 1;
256 auto const overpaymentInterestRate =
TenthBips32{10'000};
257 auto const overpaymentFeeRate =
TenthBips32{50'000};
258 auto const managementFeeRate =
TenthBips16{10'000};
260 auto const expectedOverpaymentFee =
Number{500};
261 auto const expectedOverpaymentInterestGross =
Number{100};
262 auto const expectedOverpaymentInterestNet =
Number{90};
263 auto const expectedOverpaymentManagementFee =
Number{10};
264 auto const expectedPrincipalPortion =
Number{400};
267 IOU, loanScale,
overpayment, overpaymentInterestRate, overpaymentFeeRate, managementFeeRate);
269 BEAST_EXPECT(components.untrackedManagementFee == expectedOverpaymentFee);
271 BEAST_EXPECT(components.untrackedInterest == expectedOverpaymentInterestNet);
273 BEAST_EXPECT(components.trackedInterestPart() == expectedOverpaymentInterestNet);
275 BEAST_EXPECT(components.trackedManagementFeeDelta == expectedOverpaymentManagementFee);
276 BEAST_EXPECT(components.trackedPrincipalDelta == expectedPrincipalPortion);
278 components.trackedManagementFeeDelta + components.untrackedInterest == expectedOverpaymentInterestGross);
281 components.trackedManagementFeeDelta + components.untrackedInterest + components.trackedPrincipalDelta +
282 components.untrackedManagementFee ==
297 Number expectedInterestPart;
301 Account const issuer{
"issuer"};
306 {.name =
"Zero interest",
309 .expectedInterestPart =
Number{0},
310 .expectedFeePart =
Number{0}},
311 {.name =
"Zero fee rate",
312 .interest =
Number{1'000},
314 .expectedInterestPart =
Number{1'000},
315 .expectedFeePart =
Number{0}},
316 {.name =
"10% fee rate",
317 .interest =
Number{1'000},
319 .expectedInterestPart =
Number{900},
320 .expectedFeePart =
Number{100}},
323 for (
auto const& tc : testCases)
325 testcase(
"computeInterestAndFeeParts: " + tc.name);
327 auto const [computedInterestPart, computedFeePart] =
328 computeInterestAndFeeParts(
IOU, tc.interest, tc.managementFeeRate, loanScale);
330 computedInterestPart == tc.expectedInterestPart,
331 "Interest part mismatch: expected " +
to_string(tc.expectedInterestPart) +
", got " +
334 computedFeePart == tc.expectedFeePart,
335 "Fee part mismatch: expected " +
to_string(tc.expectedFeePart) +
", got " +
to_string(computedFeePart));
347 Number principalOutstanding;
351 Number expectedLateInterest;
356 .name =
"On-time payment",
357 .principalOutstanding =
Number{1'000},
360 .nextPaymentDueDate = 3'000,
361 .expectedLateInterest =
Number{0},
364 .name =
"Early payment",
365 .principalOutstanding =
Number{1'000},
368 .nextPaymentDueDate = 4'000,
369 .expectedLateInterest =
Number{0},
372 .name =
"No principal outstanding",
373 .principalOutstanding =
Number{0},
376 .nextPaymentDueDate = 2'000,
377 .expectedLateInterest =
Number{0},
380 .name =
"No late interest rate",
381 .principalOutstanding =
Number{1'000},
384 .nextPaymentDueDate = 2'000,
385 .expectedLateInterest =
Number{0},
388 .name =
"Late payment",
389 .principalOutstanding =
Number{1'000},
392 .nextPaymentDueDate = 2'000,
393 .expectedLateInterest =
Number{317097919837645865, -19},
397 for (
auto const& tc : testCases)
399 testcase(
"loanLatePaymentInterest: " + tc.name);
401 auto const computedLateInterest = loanLatePaymentInterest(
402 tc.principalOutstanding, tc.lateInterestRate, tc.parentCloseTime, tc.nextPaymentDueDate);
404 computedLateInterest == tc.expectedLateInterest,
405 "Late interest mismatch: expected " +
to_string(tc.expectedLateInterest) +
", got " +
418 Number principalOutstanding;
424 Number expectedAccruedInterest;
429 .name =
"Zero principal outstanding",
430 .principalOutstanding =
Number{0},
431 .periodicRate =
Number{5, -2},
434 .prevPaymentDate = 2'500,
435 .paymentInterval = 30 * 24 * 60 * 60,
436 .expectedAccruedInterest =
Number{0},
439 .name =
"Before start date",
440 .principalOutstanding =
Number{1'000},
441 .periodicRate =
Number{5, -2},
444 .prevPaymentDate = 1'500,
445 .paymentInterval = 30 * 24 * 60 * 60,
446 .expectedAccruedInterest =
Number{0},
449 .name =
"Zero periodic rate",
450 .principalOutstanding =
Number{1'000},
451 .periodicRate =
Number{0},
454 .prevPaymentDate = 2'500,
455 .paymentInterval = 30 * 24 * 60 * 60,
456 .expectedAccruedInterest =
Number{0},
459 .name =
"Zero payment interval",
460 .principalOutstanding =
Number{1'000},
461 .periodicRate =
Number{5, -2},
464 .prevPaymentDate = 2'500,
465 .paymentInterval = 0,
466 .expectedAccruedInterest =
Number{0},
469 .name =
"Standard case",
470 .principalOutstanding =
Number{1'000},
471 .periodicRate =
Number{5, -2},
474 .prevPaymentDate = 2'000,
475 .paymentInterval = 30 * 24 * 60 * 60,
476 .expectedAccruedInterest =
Number{1929012345679012346, -20},
480 for (
auto const& tc : testCases)
482 testcase(
"loanAccruedInterest: " + tc.name);
484 auto const computedAccruedInterest = loanAccruedInterest(
485 tc.principalOutstanding,
492 computedAccruedInterest == tc.expectedAccruedInterest,
493 "Accrued interest mismatch: expected " +
to_string(tc.expectedAccruedInterest) +
", got " +
509 Number rawPrincipalOutstanding;
516 Number expectedFullPaymentInterest;
521 .name =
"Zero principal outstanding",
522 .rawPrincipalOutstanding =
Number{0},
523 .periodicRate =
Number{5, -2},
525 .paymentInterval = 30 * 24 * 60 * 60,
526 .prevPaymentDate = 2'000,
529 .expectedFullPaymentInterest =
Number{0},
532 .name =
"Zero close interest rate",
533 .rawPrincipalOutstanding =
Number{1'000},
534 .periodicRate =
Number{5, -2},
536 .paymentInterval = 30 * 24 * 60 * 60,
537 .prevPaymentDate = 2'000,
540 .expectedFullPaymentInterest =
Number{1929012345679012346, -20},
543 .name =
"Standard case",
544 .rawPrincipalOutstanding =
Number{1'000},
545 .periodicRate =
Number{5, -2},
547 .paymentInterval = 30 * 24 * 60 * 60,
548 .prevPaymentDate = 2'000,
551 .expectedFullPaymentInterest =
Number{1000192901234567901, -16},
555 for (
auto const& tc : testCases)
557 testcase(
"computeFullPaymentInterest: " + tc.name);
560 tc.rawPrincipalOutstanding,
566 tc.closeInterestRate);
568 computedFullPaymentInterest == tc.expectedFullPaymentInterest,
569 "Full payment interest mismatch: expected " +
to_string(tc.expectedFullPaymentInterest) +
", got " +
578 testcase(
"tryOverpayment - No Interest No Fee");
584 Account const issuer{
"issuer"};
589 Number const loanPrincipal{1'000};
592 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
593 Number const overpaymentAmount{50};
599 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
601 Number const periodicPayment = loanProperites.periodicPayment;
603 auto const ret = tryOverpayment(
606 overpaymentComponents,
607 loanProperites.loanState,
616 auto const& [actualPaymentParts, newLoanProperties] = *ret;
617 auto const& newState = newLoanProperties.loanState;
621 actualPaymentParts.valueChange == 0,
622 " valueChange mismatch: expected 0, got " +
to_string(actualPaymentParts.valueChange));
625 actualPaymentParts.feePaid == 0,
626 " feePaid mismatch: expected 0, got " +
to_string(actualPaymentParts.feePaid));
629 actualPaymentParts.interestPaid == 0,
630 " interestPaid mismatch: expected 0, got " +
to_string(actualPaymentParts.interestPaid));
633 actualPaymentParts.principalPaid == overpaymentAmount,
634 " principalPaid mismatch: expected " +
to_string(overpaymentAmount) +
", got " +
635 to_string(actualPaymentParts.principalPaid));
639 loanProperites.loanState.interestDue - newState.interestDue == 0,
640 " interest change mismatch: expected 0, got " +
641 to_string(loanProperites.loanState.interestDue - newState.interestDue));
644 loanProperites.loanState.managementFeeDue - newState.managementFeeDue == 0,
645 " management fee change mismatch: expected 0, got " +
646 to_string(loanProperites.loanState.managementFeeDue - newState.managementFeeDue));
649 actualPaymentParts.principalPaid ==
650 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
651 " principalPaid mismatch: expected " +
652 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
653 to_string(actualPaymentParts.principalPaid));
659 testcase(
"tryOverpayment - No Interest With Overpayment Fee");
665 Account const issuer{
"issuer"};
670 Number const loanPrincipal{1'000};
673 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
684 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
686 Number const periodicPayment = loanProperites.periodicPayment;
688 auto const ret = tryOverpayment(
691 overpaymentComponents,
692 loanProperites.loanState,
701 auto const& [actualPaymentParts, newLoanProperties] = *ret;
702 auto const& newState = newLoanProperties.loanState;
706 actualPaymentParts.valueChange == 0,
707 " valueChange mismatch: expected 0, got " +
to_string(actualPaymentParts.valueChange));
710 actualPaymentParts.feePaid == 5,
711 " feePaid mismatch: expected 5, got " +
to_string(actualPaymentParts.feePaid));
714 actualPaymentParts.principalPaid == 45,
715 " principalPaid mismatch: expected 45, got `" +
to_string(actualPaymentParts.principalPaid));
718 actualPaymentParts.interestPaid == 0,
719 " interestPaid mismatch: expected 0, got " +
to_string(actualPaymentParts.interestPaid));
724 loanProperites.loanState.interestDue - newState.interestDue == 0,
725 " interest change mismatch: expected 0, got " +
726 to_string(loanProperites.loanState.interestDue - newState.interestDue));
730 loanProperites.loanState.managementFeeDue - newState.managementFeeDue == 0,
731 " management fee change mismatch: expected 0, got " +
732 to_string(loanProperites.loanState.managementFeeDue - newState.managementFeeDue));
735 actualPaymentParts.principalPaid ==
736 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
737 " principalPaid mismatch: expected " +
738 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
739 to_string(actualPaymentParts.principalPaid));
745 testcase(
"tryOverpayment - Loan Interest, No Overpayment Fees");
751 Account const issuer{
"issuer"};
756 Number const loanPrincipal{1'000};
759 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
770 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
772 Number const periodicPayment = loanProperites.periodicPayment;
774 auto const ret = tryOverpayment(
777 overpaymentComponents,
778 loanProperites.loanState,
787 auto const& [actualPaymentParts, newLoanProperties] = *ret;
788 auto const& newState = newLoanProperties.loanState;
794 (actualPaymentParts.valueChange ==
Number{-228802, -5}),
795 " valueChange mismatch: expected " +
to_string(
Number{-228802, -5}) +
", got " +
796 to_string(actualPaymentParts.valueChange));
800 actualPaymentParts.feePaid == 0,
801 " feePaid mismatch: expected 0, got " +
to_string(actualPaymentParts.feePaid));
804 actualPaymentParts.principalPaid == 50,
805 " principalPaid mismatch: expected 50, got `" +
to_string(actualPaymentParts.principalPaid));
809 actualPaymentParts.interestPaid == 0,
810 " interestPaid mismatch: expected 0, got " +
to_string(actualPaymentParts.interestPaid));
814 actualPaymentParts.principalPaid ==
815 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
816 " principalPaid mismatch: expected " +
817 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
818 to_string(actualPaymentParts.principalPaid));
821 actualPaymentParts.valueChange == newState.interestDue - loanProperites.loanState.interestDue,
822 " valueChange mismatch: expected " +
823 to_string(newState.interestDue - loanProperites.loanState.interestDue) +
", got " +
824 to_string(actualPaymentParts.valueChange));
828 loanProperites.loanState.managementFeeDue - newState.managementFeeDue == 0,
829 " management fee change mismatch: expected 0, got " +
830 to_string(loanProperites.loanState.managementFeeDue - newState.managementFeeDue));
836 testcase(
"tryOverpayment - Loan Interest, Overpayment Interest, No Fee");
842 Account const issuer{
"issuer"};
847 Number const loanPrincipal{1'000};
850 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
861 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
863 Number const periodicPayment = loanProperites.periodicPayment;
865 auto const ret = tryOverpayment(
868 overpaymentComponents,
869 loanProperites.loanState,
878 auto const& [actualPaymentParts, newLoanProperties] = *ret;
879 auto const& newState = newLoanProperties.loanState;
884 actualPaymentParts.interestPaid == 5,
885 " interestPaid mismatch: expected 5, got " +
to_string(actualPaymentParts.interestPaid));
890 (actualPaymentParts.valueChange ==
Number{-205922, -5} + actualPaymentParts.interestPaid),
891 " valueChange mismatch: expected " +
892 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid) +
", got " +
893 to_string(actualPaymentParts.valueChange));
897 actualPaymentParts.feePaid == 0,
898 " feePaid mismatch: expected 0, got " +
to_string(actualPaymentParts.feePaid));
901 actualPaymentParts.principalPaid == 45,
902 " principalPaid mismatch: expected 45, got `" +
to_string(actualPaymentParts.principalPaid));
906 actualPaymentParts.principalPaid ==
907 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
908 " principalPaid mismatch: expected " +
909 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
910 to_string(actualPaymentParts.principalPaid));
915 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
916 newState.interestDue - loanProperites.loanState.interestDue,
917 " valueChange mismatch: expected " +
919 newState.interestDue - loanProperites.loanState.interestDue + actualPaymentParts.interestPaid) +
920 ", got " +
to_string(actualPaymentParts.valueChange));
924 loanProperites.loanState.managementFeeDue - newState.managementFeeDue == 0,
925 " management fee change mismatch: expected 0, got " +
926 to_string(loanProperites.loanState.managementFeeDue - newState.managementFeeDue));
933 "tryOverpayment - Loan Interest and Fee, Overpayment Interest, No "
940 Account const issuer{
"issuer"};
945 Number const loanPrincipal{1'000};
948 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
959 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
961 Number const periodicPayment = loanProperites.periodicPayment;
963 auto const ret = tryOverpayment(
966 overpaymentComponents,
967 loanProperites.loanState,
976 auto const& [actualPaymentParts, newLoanProperties] = *ret;
977 auto const& newState = newLoanProperties.loanState;
984 (actualPaymentParts.interestPaid ==
Number{45, -1}),
985 " interestPaid mismatch: expected 4.5, got " +
to_string(actualPaymentParts.interestPaid));
990 (actualPaymentParts.valueChange ==
Number{-18533, -4} + actualPaymentParts.interestPaid),
991 " valueChange mismatch: expected " +
to_string(
Number{-18533, -4} + actualPaymentParts.interestPaid) +
992 ", got " +
to_string(actualPaymentParts.valueChange));
997 (actualPaymentParts.feePaid ==
Number{5, -1}),
998 " feePaid mismatch: expected 0.5, got " +
to_string(actualPaymentParts.feePaid));
1001 actualPaymentParts.principalPaid == 45,
1002 " principalPaid mismatch: expected 45, got `" +
to_string(actualPaymentParts.principalPaid));
1006 actualPaymentParts.principalPaid ==
1007 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
1008 " principalPaid mismatch: expected " +
1009 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
1010 to_string(actualPaymentParts.principalPaid));
1015 (newState.managementFeeDue - loanProperites.loanState.managementFeeDue ==
Number{-20592, -5}),
1016 " management fee change mismatch: expected " +
to_string(
Number{-20592, -5}) +
", got " +
1017 to_string(newState.managementFeeDue - loanProperites.loanState.managementFeeDue));
1020 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
1021 newState.interestDue - loanProperites.loanState.interestDue,
1022 " valueChange mismatch: expected " +
1023 to_string(newState.interestDue - loanProperites.loanState.interestDue) +
", got " +
1024 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid));
1030 testcase(
"tryOverpayment - Loan Interest, Fee, Overpayment Interest, Fee");
1032 using namespace jtx;
1036 Account const issuer{
"issuer"};
1041 Number const loanPrincipal{1'000};
1044 auto const periodicRate =
loanPeriodicRate(loanInterestRate, paymentInterval);
1055 asset, loanPrincipal, loanInterestRate, paymentInterval, paymentsRemaining, managementFeeRate, loanScale);
1057 Number const periodicPayment = loanProperites.periodicPayment;
1059 auto const ret = tryOverpayment(
1062 overpaymentComponents,
1063 loanProperites.loanState,
1072 auto const& [actualPaymentParts, newLoanProperties] = *ret;
1073 auto const& newState = newLoanProperties.loanState;
1080 (actualPaymentParts.interestPaid ==
Number{45, -1}),
1081 " interestPaid mismatch: expected 4.5, got " +
to_string(actualPaymentParts.interestPaid));
1086 (actualPaymentParts.valueChange ==
Number{-164737, -5} + actualPaymentParts.interestPaid),
1087 " valueChange mismatch: expected " +
to_string(
Number{-164737, -5} + actualPaymentParts.interestPaid) +
1088 ", got " +
to_string(actualPaymentParts.valueChange));
1093 (actualPaymentParts.feePaid ==
Number{55, -1}),
1094 " feePaid mismatch: expected 5.5, got " +
to_string(actualPaymentParts.feePaid));
1097 actualPaymentParts.principalPaid == 40,
1098 " principalPaid mismatch: expected 40, got `" +
to_string(actualPaymentParts.principalPaid));
1103 actualPaymentParts.principalPaid ==
1104 loanProperites.loanState.principalOutstanding - newState.principalOutstanding,
1105 " principalPaid mismatch: expected " +
1106 to_string(loanProperites.loanState.principalOutstanding - newState.principalOutstanding) +
", got " +
1107 to_string(actualPaymentParts.principalPaid));
1112 (newState.managementFeeDue - loanProperites.loanState.managementFeeDue ==
Number{-18304, -5}),
1113 " management fee change mismatch: expected " +
to_string(
Number{-18304, -5}) +
", got " +
1114 to_string(newState.managementFeeDue - loanProperites.loanState.managementFeeDue));
1117 actualPaymentParts.valueChange - actualPaymentParts.interestPaid ==
1118 newState.interestDue - loanProperites.loanState.interestDue,
1119 " valueChange mismatch: expected " +
1120 to_string(newState.interestDue - loanProperites.loanState.interestDue) +
", got " +
1121 to_string(actualPaymentParts.valueChange - actualPaymentParts.interestPaid));