You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2026/03/20 12:16:25 UTC
(fineract) branch develop updated: FINERACT-2455: Add projected amortization schedule
This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new c26fc075a1 FINERACT-2455: Add projected amortization schedule
c26fc075a1 is described below
commit c26fc075a122e6ebe55d618a5fe2afdee39e926c
Author: Oleksii Novikov <al...@onix-systems.com>
AuthorDate: Fri Feb 27 13:34:36 2026 +0200
FINERACT-2455: Add projected amortization schedule
---
.../src/docs/en/chapters/features/index.adoc | 1 +
.../working-capital-amortization-schedule.adoc | 201 ++
.../WorkingCapitalAmortizationScheduleStepDef.java | 203 ++
.../WorkingCapitalAmortizationSchedule.feature | 216 ++
.../api/InternalWorkingCapitalLoanApiResource.java | 80 +
...CapitalLoanAmortizationScheduleApiResource.java | 68 +
...ultProjectedAmortizationScheduleCalculator.java | 57 +
.../ProjectedAmortizationScheduleCalculator.java | 73 +
.../calc/ProjectedAmortizationScheduleModel.java | 430 ++++
.../workingcapitalloan/calc/ProjectedPayment.java | 80 +
.../workingcapitalloan/calc/TvmFunctions.java | 157 ++
.../data/ProjectedAmortizationScheduleData.java | 43 +
...ojectedAmortizationScheduleGenerateRequest.java | 39 +
.../ProjectedAmortizationSchedulePaymentData.java | 48 +
.../domain/ProjectedAmortizationLoanModel.java | 57 +
...ectedAmortizationScheduleNotFoundException.java | 29 +
.../ProjectedAmortizationScheduleMapper.java | 77 +
.../ProjectedAmortizationLoanModelRepository.java | 31 +
...ctedAmortizationScheduleModelParserService.java | 33 +
...tizationScheduleModelParserServiceGsonImpl.java | 87 +
...ectedAmortizationScheduleRepositoryWrapper.java | 34 +
...dAmortizationScheduleRepositoryWrapperImpl.java | 66 +
...CapitalLoanAmortizationScheduleReadService.java | 26 +
...talLoanAmortizationScheduleReadServiceImpl.java | 58 +
...apitalLoanAmortizationScheduleWriteService.java | 26 +
...alLoanAmortizationScheduleWriteServiceImpl.java | 65 +
.../workingcapitalloan/module-changelog-master.xml | 1 +
.../parts/0009_wc_loan_amortization_model.xml | 81 +
...rojectedAmortizationScheduleCalculatorTest.java | 2316 ++++++++++++++++++++
29 files changed, 4683 insertions(+)
diff --git a/fineract-doc/src/docs/en/chapters/features/index.adoc b/fineract-doc/src/docs/en/chapters/features/index.adoc
index 5c386f1e1e..c7a212145d 100644
--- a/fineract-doc/src/docs/en/chapters/features/index.adoc
+++ b/fineract-doc/src/docs/en/chapters/features/index.adoc
@@ -16,3 +16,4 @@ include::re-amortization.adoc[leveloffset=+1]
include::re-ageing.adoc[leveloffset=+1]
include::delayed-schedule-captures.adoc[leveloffset=+1]
include::loan-origination-details.adoc[leveloffset=+1]
+include::working-capital-amortization-schedule.adoc[leveloffset=+1]
diff --git a/fineract-doc/src/docs/en/chapters/features/working-capital-amortization-schedule.adoc b/fineract-doc/src/docs/en/chapters/features/working-capital-amortization-schedule.adoc
new file mode 100644
index 0000000000..a0d7ab5ff6
--- /dev/null
+++ b/fineract-doc/src/docs/en/chapters/features/working-capital-amortization-schedule.adoc
@@ -0,0 +1,201 @@
+= Working Capital Loan — Projected Amortization Schedule
+
+== Overview
+
+Dynamically updated amortization schedule for Working Capital (WC) Loans — zero-interest, discount/fee-based products with flexible, sales-based repayments. Uses EIR methodology for income recognition, deferral, and NPV calculation.
+
+Each payment period is exactly *one day*: period `N` date = `expectedDisbursementDate + N days`.
+
+== Lifecycle
+
+[cols="1,3",options="header"]
+|===
+| Stage | Behavior
+
+| *Create* | `generateModel()` — initial schedule from application parameters.
+| *Disburse* | `addDisbursement()` — recalculates with actual disbursement amounts/date, preserves applied payments.
+| *Repayment* | `applyPayment()` — records payment, full schedule rebuild.
+|===
+
+== Input Parameters
+
+[cols="2,1,4",options="header"]
+|===
+| Parameter | Type | Description
+
+| `originationFeeAmount` | Money | Upfront discount/fee. Amortized over lifecycle.
+| `netDisbursementAmount` | Money | Principal disbursed. Must be positive.
+| `totalPaymentValue` | Money | Merchant's TPV.
+| `periodPaymentRate` | BigDecimal | Rate applied to TPV (e.g., 0.18 = 18%).
+| `npvDayCount` | Integer | Day-count convention (e.g., 360). Must be positive.
+| `expectedDisbursementDate` | Date | Loan start date.
+|===
+
+== Formulas
+
+----
+expectedPaymentAmount = (TPV × periodPaymentRate) / npvDayCount
+loanTerm = roundUp((netDisbursement + originationFee) / expectedPayment)
+EIR = RATE(loanTerm, -expectedPayment, netDisbursement) // Newton-Raphson, tol=1E-10, max 100 iter
+paymentsLeft = max(0, paymentNo - appliedPaymentCount)
+discountFactor = 1 / (1 + EIR) ^ paymentsLeft // model wrapper: returns 1.0 if ≤ 0
+npvSource = actualPayment (if applied) or forecastPayment (if not)
+npvValue = max(0, npvSource × discountFactor) // row 0: -netDisbursementAmount (unclamped)
+----
+
+== Schedule Fields
+
+[cols="2,4",options="header"]
+|===
+| Field | Formula / Description
+
+| `paymentNo` | 1-based. Row 0 = disbursement.
+| `paymentDate` | `expectedDisbursementDate + paymentNo` days.
+| `count` | `loanTerm + appliedPaymentCount - paymentNo`. Can be negative for tail periods.
+| `paymentsLeft` | `max(0, paymentNo - appliedPaymentCount)`. Zero for row 0.
+| `expectedPaymentAmount` | Constant. Row 0: `-netDisbursementAmount`. Tail: null.
+| `forecastPaymentAmount` | Expected payment reduced by excess (backward from last period), clamped to zero. Row 0: null.
+| `discountFactor` | `1/(1+EIR)^paymentsLeft`. Row 0 and paid periods: 1.0.
+| `npvValue` | `max(0, npvSource × DF)`. Row 0: `-netDisbursementAmount`.
+| `balance` | `balance[i-1]×(1+EIR) - expectedPayment`. Row 0: `+netDisbursementAmount`. Tail: null.
+| `expectedAmortizationAmount` | `min(balance[i] + expectedPayment - balance[i-1], originationFee)`. Row 0 / tail: null.
+| `netAmortizationAmount` | Paid: `min(totalNetAmort - cumulativeActualAmort_before, originationFee)` where `cumulativeActualAmort_before` excludes the current period. Unpaid: 0. Row 0: null.
+| `actualPaymentAmount` | Actual cash paid. Null if no payment.
+| `actualAmortizationAmount` | Cursor-based: `actualPayment/expectedPayment` periods of expected amortization consumed. Null if no payment.
+| `incomeModification` | Paid: `actualAmort - expectedAmort`. Unpaid: `-expectedAmort`. Row 0 / tail: null.
+| `deferredBalance` | `originationFee - cumulativeActualAmort`. Row 0: `originationFee`. Tail: null.
+|===
+
+=== Disbursement Row (paymentNo = 0)
+
+[cols="1,2"]
+|===
+| `count` | `loanTerm + appliedPaymentCount`
+| `paymentsLeft` | 0
+| `expectedPaymentAmount` | `-netDisbursementAmount`
+| `forecastPaymentAmount` | null
+| `discountFactor` | 1.0
+| `npvValue` | `-netDisbursementAmount`
+| `balance` | `+netDisbursementAmount`
+| `deferredBalance` | `originationFeeAmount`
+| all other nullable fields | null
+|===
+
+=== Tail Periods
+
+Appended when shortfall remains after loanTerm. Each forecasts `min(remainingShortfall, expectedPayment)`. Structural fields (`paymentNo`, `paymentDate`, `count`, `paymentsLeft`) are always set. Among nullable fields, only `forecastPaymentAmount`, `discountFactor`, `npvValue`, `netAmortizationAmount` (zero) are populated; rest are null. Trailing rows with zero forecast are trimmed.
+
+== Calculation Algorithm
+
+. *Balances & expected amortizations*: `balance[i] = balance[i-1]×(1+EIR) - expectedPayment`. Expected amort capped at `originationFee`.
+. *Aggregate payments by date* (same-date payments summed). Payments must occupy consecutive day slots starting from day 1.
+. *Shortfall/excess analysis*: compare each applied payment to expected.
+. *Cursor-based actual amortization*: cursor advances by `actualPayment/expectedPayment` periods; interpolates partial periods.
+. *Excess distribution*: reduces forecast payments backward from last period.
+. *Tail periods*: appended for remaining shortfall.
+. *Total net amortization*: `-netDisbursement + Σ(npvSource[i] × DF[i]) + tailNpv`.
+. *Assemble rows*, trim trailing zero-forecast.
+
+== API Endpoints
+
+=== GET /v1/working-capital-loans/{loanId}/amortization-schedule
+
+Example Request:
+
+ GET /fineract-provider/api/v1/working-capital-loans/1/amortization-schedule
+
+Example Response (first 3 payments shown):
+
+[source,json]
+----
+{
+ "originationFeeAmount": 1000.00,
+ "netDisbursementAmount": 9000.00,
+ "totalPaymentValue": 100000.00,
+ "periodPaymentRate": 0.18,
+ "npvDayCount": 360,
+ "expectedDisbursementDate": "2019-01-01",
+ "expectedPaymentAmount": 50.00,
+ "loanTerm": 200,
+ "effectiveInterestRate": 0.0010678144878363462,
+ "payments": [
+ {
+ "paymentNo": 0,
+ "paymentDate": "2019-01-01",
+ "count": 200,
+ "paymentsLeft": 0,
+ "expectedPaymentAmount": -9000.00,
+ "forecastPaymentAmount": null,
+ "discountFactor": 1,
+ "npvValue": -9000.00,
+ "balance": 9000.00,
+ "expectedAmortizationAmount": null,
+ "netAmortizationAmount": null,
+ "actualPaymentAmount": null,
+ "actualAmortizationAmount": null,
+ "incomeModification": null,
+ "deferredBalance": 1000.00
+ },
+ {
+ "paymentNo": 1,
+ "paymentDate": "2019-01-02",
+ "count": 199,
+ "paymentsLeft": 1,
+ "expectedPaymentAmount": 50.00,
+ "forecastPaymentAmount": 50.00,
+ "discountFactor": 0.998933324523691,
+ "npvValue": 49.95,
+ "balance": 8959.61,
+ "expectedAmortizationAmount": 9.61,
+ "netAmortizationAmount": 0.00,
+ "actualPaymentAmount": null,
+ "actualAmortizationAmount": null,
+ "incomeModification": -9.61,
+ "deferredBalance": 1000.00
+ },
+ {
+ "paymentNo": 2,
+ "paymentDate": "2019-01-03",
+ "count": 198,
+ "paymentsLeft": 2,
+ "expectedPaymentAmount": 50.00,
+ "forecastPaymentAmount": 50.00,
+ "discountFactor": 0.9978677868439537,
+ "npvValue": 49.89,
+ "balance": 8919.18,
+ "expectedAmortizationAmount": 9.57,
+ "netAmortizationAmount": 0.00,
+ "actualPaymentAmount": null,
+ "actualAmortizationAmount": null,
+ "incomeModification": -9.57,
+ "deferredBalance": 1000.00
+ }
+ ]
+}
+----
+
+== Database
+
+.Table: `m_wc_loan_amortization_model`
+[cols="2,1,3",options="header"]
+|===
+| Column | Type | Description
+
+| `id` | BIGINT | PK, auto-increment.
+| `version` | INT | Optimistic lock.
+| `loan_id` | BIGINT | FK → `m_wc_loan`. Unique.
+| `json_model` | LONGTEXT (MySQL/MariaDB) / TEXT (PostgreSQL) | Serialized model (Gson). `@JsonExclude` on `MathContext`, `MonetaryCurrency`; restored via `forDeserialization()`.
+| `business_date` | DATE | Last generated/updated.
+| `last_modified_on_utc` | DATETIME(6) / TIMESTAMP WITH TIME ZONE | Audit. MySQL / PostgreSQL.
+| `json_model_version` | VARCHAR(10) | Currently `"1"`.
+|===
+
+=== Rebuild Flow
+
+Every state change triggers full rebuild: aggregate payments → build payment list (1 to loanTerm, actual or null) → balances → payment analysis → cursor amortizations → excess distribution → tail → net amortization → assemble rows → trim.
+
+== TVM Functions
+
+*RATE*: Newton-Raphson solving `pv×(1+r)^n + pmt×((1+r)^n−1)/r = 0`. Initial guess: `2×(pmt×n+pv)/(pv×n)`, fallback 0.01. Tolerance 1E-10, max 100 iterations.
+
+*Discount Factor*: `1/(1+eir)^days`. Returns 1.0 when days=0. Throws `IllegalArgumentException` for negative days. The model wraps this via `safeDiscountFactor()` which additionally returns 1.0 when the computed result is ≤ 0.
diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalAmortizationScheduleStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalAmortizationScheduleStepDef.java
new file mode 100644
index 0000000000..b920f0e0e7
--- /dev/null
+++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/WorkingCapitalAmortizationScheduleStepDef.java
@@ -0,0 +1,203 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.test.stepdef.loan;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.cucumber.datatable.DataTable;
+import io.cucumber.java.en.Then;
+import io.cucumber.java.en.When;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.feign.FineractFeignClient;
+import org.apache.fineract.client.feign.services.WorkingCapitalLoansApi;
+import org.apache.fineract.client.models.PostWorkingCapitalLoansResponse;
+import org.apache.fineract.client.models.ProjectedAmortizationScheduleData;
+import org.apache.fineract.client.models.ProjectedAmortizationScheduleGenerateRequest;
+import org.apache.fineract.client.models.ProjectedAmortizationSchedulePaymentData;
+import org.apache.fineract.test.stepdef.AbstractStepDef;
+import org.apache.fineract.test.support.TestContext;
+import org.apache.fineract.test.support.TestContextKey;
+import org.assertj.core.api.SoftAssertions;
+
+@Slf4j
+@RequiredArgsConstructor
+public class WorkingCapitalAmortizationScheduleStepDef extends AbstractStepDef {
+
+ private static final String WC_AMORT_SCHEDULE_KEY = "WC_AMORT_SCHEDULE_RESPONSE";
+
+ private final FineractFeignClient fineractFeignClient;
+
+ @When("Admin generates a projected amortization schedule with originationFeeAmount {double}, netDisbursementAmount {double}, totalPaymentValue {double}, periodPaymentRate {double}, npvDayCount {int}, expectedDisbursementDate {string}")
+ public void generateAmortizationSchedule(final double originationFeeAmount, final double netDisbursementAmount,
+ final double totalPaymentValue, final double periodPaymentRate, final int npvDayCount, final String expectedDisbursementDate) {
+ final Long loanId = extractLoanId();
+ final WorkingCapitalLoansApi api = fineractFeignClient.create(WorkingCapitalLoansApi.class);
+
+ final ProjectedAmortizationScheduleGenerateRequest request = new ProjectedAmortizationScheduleGenerateRequest();
+ request.setOriginationFeeAmount(BigDecimal.valueOf(originationFeeAmount));
+ request.setNetDisbursementAmount(BigDecimal.valueOf(netDisbursementAmount));
+ request.setTotalPaymentValue(BigDecimal.valueOf(totalPaymentValue));
+ request.setPeriodPaymentRate(BigDecimal.valueOf(periodPaymentRate));
+ request.setNpvDayCount(npvDayCount);
+ request.setExpectedDisbursementDate(LocalDate.parse(expectedDisbursementDate));
+
+ api.generateAmortizationSchedule(loanId, request);
+ log.info("Generated amortization schedule for loan {}", loanId);
+ }
+
+ @When("Admin retrieves the projected amortization schedule")
+ public void retrieveAmortizationSchedule() {
+ final Long loanId = extractLoanId();
+ final WorkingCapitalLoansApi api = fineractFeignClient.create(WorkingCapitalLoansApi.class);
+
+ final ProjectedAmortizationScheduleData response = api.retrieveAmortizationSchedule(loanId);
+ log.info("Retrieved amortization schedule for loan {}: netDisbursementAmount={}", loanId, response.getNetDisbursementAmount());
+ TestContext.INSTANCE.set(WC_AMORT_SCHEDULE_KEY, response);
+ }
+
+ @Then("The retrieved amortization schedule has the following summary fields:")
+ public void verifyRetrievedSummaryFields(final DataTable dataTable) {
+ verifySummaryFields(dataTable);
+ }
+
+ @Then("The retrieved amortization schedule has payments with the following details:")
+ public void verifyRetrievedPaymentDetails(final DataTable dataTable) {
+ verifyPaymentDetails(dataTable);
+ }
+
+ private void verifySummaryFields(final DataTable dataTable) {
+ final ProjectedAmortizationScheduleData response = TestContext.INSTANCE.get(WC_AMORT_SCHEDULE_KEY);
+ assertThat(response).as("Amortization schedule response").isNotNull();
+
+ final Map<String, String> expected = dataTable.asMaps().getFirst();
+ final SoftAssertions assertions = new SoftAssertions();
+
+ assertDecimal(assertions, "originationFeeAmount", response.getOriginationFeeAmount(), expected.get("originationFeeAmount"));
+ assertDecimal(assertions, "netDisbursementAmount", response.getNetDisbursementAmount(), expected.get("netDisbursementAmount"));
+ assertDecimal(assertions, "totalPaymentValue", response.getTotalPaymentValue(), expected.get("totalPaymentValue"));
+ assertDecimal(assertions, "periodPaymentRate", response.getPeriodPaymentRate(), expected.get("periodPaymentRate"));
+ assertInt(assertions, "npvDayCount", response.getNpvDayCount(), expected.get("npvDayCount"));
+ assertDecimal(assertions, "expectedPaymentAmount", response.getExpectedPaymentAmount(), expected.get("expectedPaymentAmount"));
+ assertInt(assertions, "loanTerm", response.getLoanTerm(), expected.get("loanTerm"));
+
+ assertions.assertAll();
+ }
+
+ private void verifyPaymentDetails(final DataTable dataTable) {
+ final ProjectedAmortizationScheduleData response = TestContext.INSTANCE.get(WC_AMORT_SCHEDULE_KEY);
+ assertThat(response).as("Amortization schedule response").isNotNull();
+
+ final List<ProjectedAmortizationSchedulePaymentData> actualPayments = response.getPayments();
+ assertThat(actualPayments).as("payments list").isNotNull();
+
+ final List<Map<String, String>> expectedRows = dataTable.asMaps();
+ assertThat(actualPayments).as("payment count").hasSameSizeAs(expectedRows);
+
+ final SoftAssertions assertions = new SoftAssertions();
+ for (int i = 0; i < expectedRows.size(); i++) {
+ final Map<String, String> expected = expectedRows.get(i);
+ final ProjectedAmortizationSchedulePaymentData actual = actualPayments.get(i);
+ final String p = "payment[" + i + "].";
+
+ assertInt(assertions, p + "paymentNo", actual.getPaymentNo(), expected.get("paymentNo"));
+ assertDate(assertions, p + "date", actual.getPaymentDate(), expected.get("date"));
+ assertLong(assertions, p + "paymentsLeft", actual.getPaymentsLeft(), expected.get("paymentsLeft"));
+ assertNullableDecimal(assertions, p + "expectedPaymentAmount", actual.getExpectedPaymentAmount(),
+ expected.get("expectedPaymentAmount"));
+ assertNullableDecimal(assertions, p + "forecastPaymentAmount", actual.getForecastPaymentAmount(),
+ expected.get("forecastPaymentAmount"));
+ assertOptionalDecimal(assertions, p + "discountFactor", actual.getDiscountFactor(), expected.get("discountFactor"));
+ assertNullableDecimal(assertions, p + "npvValue", actual.getNpvValue(), expected.get("npvValue"));
+ assertNullableDecimal(assertions, p + "balance", actual.getBalance(), expected.get("balance"));
+ assertNullableDecimal(assertions, p + "expectedAmortizationAmount", actual.getExpectedAmortizationAmount(),
+ expected.get("expectedAmortizationAmount"));
+ assertNullableDecimal(assertions, p + "netAmortizationAmount", actual.getNetAmortizationAmount(),
+ expected.get("netAmortizationAmount"));
+ assertNullableDecimal(assertions, p + "actualPaymentAmount", actual.getActualPaymentAmount(),
+ expected.get("actualPaymentAmount"));
+ assertNullableDecimal(assertions, p + "actualAmortizationAmount", actual.getActualAmortizationAmount(),
+ expected.get("actualAmortizationAmount"));
+ assertNullableDecimal(assertions, p + "incomeModification", actual.getIncomeModification(), expected.get("incomeModification"));
+ assertNullableDecimal(assertions, p + "deferredBalance", actual.getDeferredBalance(), expected.get("deferredBalance"));
+ }
+
+ assertions.assertAll();
+ }
+
+ private static void assertDecimal(final SoftAssertions assertions, final String field, final BigDecimal actual,
+ final String expectedStr) {
+ if (expectedStr == null || expectedStr.isBlank()) {
+ return;
+ }
+ final BigDecimal expected = new BigDecimal(expectedStr);
+ assertions.assertThat(actual).as(field).isNotNull();
+ if (actual != null) {
+ assertions.assertThat(actual.compareTo(expected)).as("%s: expected=%s actual=%s", field, expected, actual).isEqualTo(0);
+ }
+ }
+
+ private static void assertNullableDecimal(final SoftAssertions assertions, final String field, final BigDecimal actual,
+ final String expectedStr) {
+ if (expectedStr == null || expectedStr.isBlank()) {
+ assertions.assertThat(actual).as(field + " should be null").isNull();
+ return;
+ }
+ assertDecimal(assertions, field, actual, expectedStr);
+ }
+
+ private static void assertInt(final SoftAssertions assertions, final String field, final Integer actual, final String expectedStr) {
+ if (expectedStr == null || expectedStr.isBlank()) {
+ return;
+ }
+ assertions.assertThat(actual).as(field).isEqualTo(Integer.parseInt(expectedStr));
+ }
+
+ private static void assertLong(final SoftAssertions assertions, final String field, final Long actual, final String expectedStr) {
+ if (expectedStr == null || expectedStr.isBlank()) {
+ return;
+ }
+ assertions.assertThat(actual).as(field).isEqualTo(Long.parseLong(expectedStr));
+ }
+
+ private static void assertDate(final SoftAssertions assertions, final String field, final LocalDate actual, final String expectedStr) {
+ if (expectedStr == null || expectedStr.isBlank()) {
+ return;
+ }
+ assertions.assertThat(actual).as(field).isEqualTo(LocalDate.parse(expectedStr));
+ }
+
+ private static void assertOptionalDecimal(final SoftAssertions assertions, final String field, final BigDecimal actual,
+ final String expectedStr) {
+ if (expectedStr == null || expectedStr.isBlank()) {
+ assertions.assertThat(actual).as(field + " should not be null").isNotNull();
+ return;
+ }
+ assertDecimal(assertions, field, actual, expectedStr);
+ }
+
+ private Long extractLoanId() {
+ final PostWorkingCapitalLoansResponse response = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+ return response.getLoanId();
+ }
+}
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalAmortizationSchedule.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalAmortizationSchedule.feature
new file mode 100644
index 0000000000..48384646ab
--- /dev/null
+++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalAmortizationSchedule.feature
@@ -0,0 +1,216 @@
+Feature: WorkingCapitalAmortizationSchedule
+
+ Scenario: Generate and retrieve a projected amortization schedule with 200 payments
+ When Admin sets the business date to "01 January 2026"
+ And Admin creates a client with random data
+ And Admin creates a working capital loan with the following data:
+ | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount |
+ | WCLP | 01 January 2026 | 01 January 2026 | 100 | 100 | 1 | 0 |
+ When Admin generates a projected amortization schedule with originationFeeAmount 1000.0, netDisbursementAmount 9000.0, totalPaymentValue 100000.0, periodPaymentRate 0.18, npvDayCount 360, expectedDisbursementDate "2019-01-01"
+ And Admin retrieves the projected amortization schedule
+ Then The retrieved amortization schedule has the following summary fields:
+ | originationFeeAmount | netDisbursementAmount | totalPaymentValue | periodPaymentRate | npvDayCount | expectedPaymentAmount | loanTerm |
+ | 1000.00 | 9000.00 | 100000.00 | 0.18 | 360 | 50.00 | 200 |
+ And The retrieved amortization schedule has payments with the following details:
+ | paymentNo | date | paymentsLeft | expectedPaymentAmount | forecastPaymentAmount | discountFactor | npvValue | balance | expectedAmortizationAmount | netAmortizationAmount | actualPaymentAmount | actualAmortizationAmount | incomeModification | deferredBalance |
+ | 0 | 2019-01-01 | 0 | -9000.00 | | 1 | -9000.00 | 9000.00 | | | | | | 1000.00 |
+ | 1 | 2019-01-02 | 1 | 50.00 | 50.00 | | 49.95 | 8959.61 | 9.61 | 0.00 | | | -9.61 | 1000.00 |
+ | 2 | 2019-01-03 | 2 | 50.00 | 50.00 | | 49.89 | 8919.18 | 9.57 | 0.00 | | | -9.57 | 1000.00 |
+ | 3 | 2019-01-04 | 3 | 50.00 | 50.00 | | 49.84 | 8878.70 | 9.52 | 0.00 | | | -9.52 | 1000.00 |
+ | 4 | 2019-01-05 | 4 | 50.00 | 50.00 | | 49.79 | 8838.18 | 9.48 | 0.00 | | | -9.48 | 1000.00 |
+ | 5 | 2019-01-06 | 5 | 50.00 | 50.00 | | 49.73 | 8797.62 | 9.44 | 0.00 | | | -9.44 | 1000.00 |
+ | 6 | 2019-01-07 | 6 | 50.00 | 50.00 | | 49.68 | 8757.01 | 9.39 | 0.00 | | | -9.39 | 1000.00 |
+ | 7 | 2019-01-08 | 7 | 50.00 | 50.00 | | 49.63 | 8716.36 | 9.35 | 0.00 | | | -9.35 | 1000.00 |
+ | 8 | 2019-01-09 | 8 | 50.00 | 50.00 | | 49.57 | 8675.67 | 9.31 | 0.00 | | | -9.31 | 1000.00 |
+ | 9 | 2019-01-10 | 9 | 50.00 | 50.00 | | 49.52 | 8634.94 | 9.26 | 0.00 | | | -9.26 | 1000.00 |
+ | 10 | 2019-01-11 | 10 | 50.00 | 50.00 | | 49.47 | 8594.16 | 9.22 | 0.00 | | | -9.22 | 1000.00 |
+ | 11 | 2019-01-12 | 11 | 50.00 | 50.00 | | 49.42 | 8553.33 | 9.18 | 0.00 | | | -9.18 | 1000.00 |
+ | 12 | 2019-01-13 | 12 | 50.00 | 50.00 | | 49.36 | 8512.47 | 9.13 | 0.00 | | | -9.13 | 1000.00 |
+ | 13 | 2019-01-14 | 13 | 50.00 | 50.00 | | 49.31 | 8471.56 | 9.09 | 0.00 | | | -9.09 | 1000.00 |
+ | 14 | 2019-01-15 | 14 | 50.00 | 50.00 | | 49.26 | 8430.60 | 9.05 | 0.00 | | | -9.05 | 1000.00 |
+ | 15 | 2019-01-16 | 15 | 50.00 | 50.00 | | 49.21 | 8389.61 | 9.00 | 0.00 | | | -9.00 | 1000.00 |
+ | 16 | 2019-01-17 | 16 | 50.00 | 50.00 | | 49.15 | 8348.56 | 8.96 | 0.00 | | | -8.96 | 1000.00 |
+ | 17 | 2019-01-18 | 17 | 50.00 | 50.00 | | 49.10 | 8307.48 | 8.91 | 0.00 | | | -8.91 | 1000.00 |
+ | 18 | 2019-01-19 | 18 | 50.00 | 50.00 | | 49.05 | 8266.35 | 8.87 | 0.00 | | | -8.87 | 1000.00 |
+ | 19 | 2019-01-20 | 19 | 50.00 | 50.00 | | 49.00 | 8225.18 | 8.83 | 0.00 | | | -8.83 | 1000.00 |
+ | 20 | 2019-01-21 | 20 | 50.00 | 50.00 | | 48.94 | 8183.96 | 8.78 | 0.00 | | | -8.78 | 1000.00 |
+ | 21 | 2019-01-22 | 21 | 50.00 | 50.00 | | 48.89 | 8142.70 | 8.74 | 0.00 | | | -8.74 | 1000.00 |
+ | 22 | 2019-01-23 | 22 | 50.00 | 50.00 | | 48.84 | 8101.39 | 8.69 | 0.00 | | | -8.69 | 1000.00 |
+ | 23 | 2019-01-24 | 23 | 50.00 | 50.00 | | 48.79 | 8060.04 | 8.65 | 0.00 | | | -8.65 | 1000.00 |
+ | 24 | 2019-01-25 | 24 | 50.00 | 50.00 | | 48.74 | 8018.65 | 8.61 | 0.00 | | | -8.61 | 1000.00 |
+ | 25 | 2019-01-26 | 25 | 50.00 | 50.00 | | 48.68 | 7977.21 | 8.56 | 0.00 | | | -8.56 | 1000.00 |
+ | 26 | 2019-01-27 | 26 | 50.00 | 50.00 | | 48.63 | 7935.73 | 8.52 | 0.00 | | | -8.52 | 1000.00 |
+ | 27 | 2019-01-28 | 27 | 50.00 | 50.00 | | 48.58 | 7894.21 | 8.47 | 0.00 | | | -8.47 | 1000.00 |
+ | 28 | 2019-01-29 | 28 | 50.00 | 50.00 | | 48.53 | 7852.63 | 8.43 | 0.00 | | | -8.43 | 1000.00 |
+ | 29 | 2019-01-30 | 29 | 50.00 | 50.00 | | 48.48 | 7811.02 | 8.39 | 0.00 | | | -8.39 | 1000.00 |
+ | 30 | 2019-01-31 | 30 | 50.00 | 50.00 | | 48.42 | 7769.36 | 8.34 | 0.00 | | | -8.34 | 1000.00 |
+ | 31 | 2019-02-01 | 31 | 50.00 | 50.00 | | 48.37 | 7727.66 | 8.30 | 0.00 | | | -8.30 | 1000.00 |
+ | 32 | 2019-02-02 | 32 | 50.00 | 50.00 | | 48.32 | 7685.91 | 8.25 | 0.00 | | | -8.25 | 1000.00 |
+ | 33 | 2019-02-03 | 33 | 50.00 | 50.00 | | 48.27 | 7644.12 | 8.21 | 0.00 | | | -8.21 | 1000.00 |
+ | 34 | 2019-02-04 | 34 | 50.00 | 50.00 | | 48.22 | 7602.28 | 8.16 | 0.00 | | | -8.16 | 1000.00 |
+ | 35 | 2019-02-05 | 35 | 50.00 | 50.00 | | 48.17 | 7560.40 | 8.12 | 0.00 | | | -8.12 | 1000.00 |
+ | 36 | 2019-02-06 | 36 | 50.00 | 50.00 | | 48.12 | 7518.47 | 8.07 | 0.00 | | | -8.07 | 1000.00 |
+ | 37 | 2019-02-07 | 37 | 50.00 | 50.00 | | 48.06 | 7476.50 | 8.03 | 0.00 | | | -8.03 | 1000.00 |
+ | 38 | 2019-02-08 | 38 | 50.00 | 50.00 | | 48.01 | 7434.48 | 7.98 | 0.00 | | | -7.98 | 1000.00 |
+ | 39 | 2019-02-09 | 39 | 50.00 | 50.00 | | 47.96 | 7392.42 | 7.94 | 0.00 | | | -7.94 | 1000.00 |
+ | 40 | 2019-02-10 | 40 | 50.00 | 50.00 | | 47.91 | 7350.31 | 7.89 | 0.00 | | | -7.89 | 1000.00 |
+ | 41 | 2019-02-11 | 41 | 50.00 | 50.00 | | 47.86 | 7308.16 | 7.85 | 0.00 | | | -7.85 | 1000.00 |
+ | 42 | 2019-02-12 | 42 | 50.00 | 50.00 | | 47.81 | 7265.97 | 7.80 | 0.00 | | | -7.80 | 1000.00 |
+ | 43 | 2019-02-13 | 43 | 50.00 | 50.00 | | 47.76 | 7223.72 | 7.76 | 0.00 | | | -7.76 | 1000.00 |
+ | 44 | 2019-02-14 | 44 | 50.00 | 50.00 | | 47.71 | 7181.44 | 7.71 | 0.00 | | | -7.71 | 1000.00 |
+ | 45 | 2019-02-15 | 45 | 50.00 | 50.00 | | 47.66 | 7139.11 | 7.67 | 0.00 | | | -7.67 | 1000.00 |
+ | 46 | 2019-02-16 | 46 | 50.00 | 50.00 | | 47.60 | 7096.73 | 7.62 | 0.00 | | | -7.62 | 1000.00 |
+ | 47 | 2019-02-17 | 47 | 50.00 | 50.00 | | 47.55 | 7054.31 | 7.58 | 0.00 | | | -7.58 | 1000.00 |
+ | 48 | 2019-02-18 | 48 | 50.00 | 50.00 | | 47.50 | 7011.84 | 7.53 | 0.00 | | | -7.53 | 1000.00 |
+ | 49 | 2019-02-19 | 49 | 50.00 | 50.00 | | 47.45 | 6969.33 | 7.49 | 0.00 | | | -7.49 | 1000.00 |
+ | 50 | 2019-02-20 | 50 | 50.00 | 50.00 | | 47.40 | 6926.77 | 7.44 | 0.00 | | | -7.44 | 1000.00 |
+ | 51 | 2019-02-21 | 51 | 50.00 | 50.00 | | 47.35 | 6884.17 | 7.40 | 0.00 | | | -7.40 | 1000.00 |
+ | 52 | 2019-02-22 | 52 | 50.00 | 50.00 | | 47.30 | 6841.52 | 7.35 | 0.00 | | | -7.35 | 1000.00 |
+ | 53 | 2019-02-23 | 53 | 50.00 | 50.00 | | 47.25 | 6798.82 | 7.31 | 0.00 | | | -7.31 | 1000.00 |
+ | 54 | 2019-02-24 | 54 | 50.00 | 50.00 | | 47.20 | 6756.08 | 7.26 | 0.00 | | | -7.26 | 1000.00 |
+ | 55 | 2019-02-25 | 55 | 50.00 | 50.00 | | 47.15 | 6713.30 | 7.21 | 0.00 | | | -7.21 | 1000.00 |
+ | 56 | 2019-02-26 | 56 | 50.00 | 50.00 | | 47.10 | 6670.47 | 7.17 | 0.00 | | | -7.17 | 1000.00 |
+ | 57 | 2019-02-27 | 57 | 50.00 | 50.00 | | 47.05 | 6627.59 | 7.12 | 0.00 | | | -7.12 | 1000.00 |
+ | 58 | 2019-02-28 | 58 | 50.00 | 50.00 | | 47.00 | 6584.67 | 7.08 | 0.00 | | | -7.08 | 1000.00 |
+ | 59 | 2019-03-01 | 59 | 50.00 | 50.00 | | 46.95 | 6541.70 | 7.03 | 0.00 | | | -7.03 | 1000.00 |
+ | 60 | 2019-03-02 | 60 | 50.00 | 50.00 | | 46.90 | 6498.68 | 6.99 | 0.00 | | | -6.99 | 1000.00 |
+ | 61 | 2019-03-03 | 61 | 50.00 | 50.00 | | 46.85 | 6455.62 | 6.94 | 0.00 | | | -6.94 | 1000.00 |
+ | 62 | 2019-03-04 | 62 | 50.00 | 50.00 | | 46.80 | 6412.51 | 6.89 | 0.00 | | | -6.89 | 1000.00 |
+ | 63 | 2019-03-05 | 63 | 50.00 | 50.00 | | 46.75 | 6369.36 | 6.85 | 0.00 | | | -6.85 | 1000.00 |
+ | 64 | 2019-03-06 | 64 | 50.00 | 50.00 | | 46.70 | 6326.16 | 6.80 | 0.00 | | | -6.80 | 1000.00 |
+ | 65 | 2019-03-07 | 65 | 50.00 | 50.00 | | 46.65 | 6282.92 | 6.76 | 0.00 | | | -6.76 | 1000.00 |
+ | 66 | 2019-03-08 | 66 | 50.00 | 50.00 | | 46.60 | 6239.63 | 6.71 | 0.00 | | | -6.71 | 1000.00 |
+ | 67 | 2019-03-09 | 67 | 50.00 | 50.00 | | 46.55 | 6196.29 | 6.66 | 0.00 | | | -6.66 | 1000.00 |
+ | 68 | 2019-03-10 | 68 | 50.00 | 50.00 | | 46.50 | 6152.91 | 6.62 | 0.00 | | | -6.62 | 1000.00 |
+ | 69 | 2019-03-11 | 69 | 50.00 | 50.00 | | 46.45 | 6109.48 | 6.57 | 0.00 | | | -6.57 | 1000.00 |
+ | 70 | 2019-03-12 | 70 | 50.00 | 50.00 | | 46.40 | 6066.00 | 6.52 | 0.00 | | | -6.52 | 1000.00 |
+ | 71 | 2019-03-13 | 71 | 50.00 | 50.00 | | 46.35 | 6022.48 | 6.48 | 0.00 | | | -6.48 | 1000.00 |
+ | 72 | 2019-03-14 | 72 | 50.00 | 50.00 | | 46.30 | 5978.91 | 6.43 | 0.00 | | | -6.43 | 1000.00 |
+ | 73 | 2019-03-15 | 73 | 50.00 | 50.00 | | 46.25 | 5935.29 | 6.38 | 0.00 | | | -6.38 | 1000.00 |
+ | 74 | 2019-03-16 | 74 | 50.00 | 50.00 | | 46.20 | 5891.63 | 6.34 | 0.00 | | | -6.34 | 1000.00 |
+ | 75 | 2019-03-17 | 75 | 50.00 | 50.00 | | 46.15 | 5847.92 | 6.29 | 0.00 | | | -6.29 | 1000.00 |
+ | 76 | 2019-03-18 | 76 | 50.00 | 50.00 | | 46.10 | 5804.17 | 6.24 | 0.00 | | | -6.24 | 1000.00 |
+ | 77 | 2019-03-19 | 77 | 50.00 | 50.00 | | 46.06 | 5760.36 | 6.20 | 0.00 | | | -6.20 | 1000.00 |
+ | 78 | 2019-03-20 | 78 | 50.00 | 50.00 | | 46.01 | 5716.52 | 6.15 | 0.00 | | | -6.15 | 1000.00 |
+ | 79 | 2019-03-21 | 79 | 50.00 | 50.00 | | 45.96 | 5672.62 | 6.10 | 0.00 | | | -6.10 | 1000.00 |
+ | 80 | 2019-03-22 | 80 | 50.00 | 50.00 | | 45.91 | 5628.68 | 6.06 | 0.00 | | | -6.06 | 1000.00 |
+ | 81 | 2019-03-23 | 81 | 50.00 | 50.00 | | 45.86 | 5584.69 | 6.01 | 0.00 | | | -6.01 | 1000.00 |
+ | 82 | 2019-03-24 | 82 | 50.00 | 50.00 | | 45.81 | 5540.65 | 5.96 | 0.00 | | | -5.96 | 1000.00 |
+ | 83 | 2019-03-25 | 83 | 50.00 | 50.00 | | 45.76 | 5496.57 | 5.92 | 0.00 | | | -5.92 | 1000.00 |
+ | 84 | 2019-03-26 | 84 | 50.00 | 50.00 | | 45.71 | 5452.44 | 5.87 | 0.00 | | | -5.87 | 1000.00 |
+ | 85 | 2019-03-27 | 85 | 50.00 | 50.00 | | 45.66 | 5408.26 | 5.82 | 0.00 | | | -5.82 | 1000.00 |
+ | 86 | 2019-03-28 | 86 | 50.00 | 50.00 | | 45.62 | 5364.03 | 5.78 | 0.00 | | | -5.78 | 1000.00 |
+ | 87 | 2019-03-29 | 87 | 50.00 | 50.00 | | 45.57 | 5319.76 | 5.73 | 0.00 | | | -5.73 | 1000.00 |
+ | 88 | 2019-03-30 | 88 | 50.00 | 50.00 | | 45.52 | 5275.44 | 5.68 | 0.00 | | | -5.68 | 1000.00 |
+ | 89 | 2019-03-31 | 89 | 50.00 | 50.00 | | 45.47 | 5231.08 | 5.63 | 0.00 | | | -5.63 | 1000.00 |
+ | 90 | 2019-04-01 | 90 | 50.00 | 50.00 | | 45.42 | 5186.66 | 5.59 | 0.00 | | | -5.59 | 1000.00 |
+ | 91 | 2019-04-02 | 91 | 50.00 | 50.00 | | 45.37 | 5142.20 | 5.54 | 0.00 | | | -5.54 | 1000.00 |
+ | 92 | 2019-04-03 | 92 | 50.00 | 50.00 | | 45.32 | 5097.69 | 5.49 | 0.00 | | | -5.49 | 1000.00 |
+ | 93 | 2019-04-04 | 93 | 50.00 | 50.00 | | 45.28 | 5053.13 | 5.44 | 0.00 | | | -5.44 | 1000.00 |
+ | 94 | 2019-04-05 | 94 | 50.00 | 50.00 | | 45.23 | 5008.53 | 5.40 | 0.00 | | | -5.40 | 1000.00 |
+ | 95 | 2019-04-06 | 95 | 50.00 | 50.00 | | 45.18 | 4963.88 | 5.35 | 0.00 | | | -5.35 | 1000.00 |
+ | 96 | 2019-04-07 | 96 | 50.00 | 50.00 | | 45.13 | 4919.18 | 5.30 | 0.00 | | | -5.30 | 1000.00 |
+ | 97 | 2019-04-08 | 97 | 50.00 | 50.00 | | 45.08 | 4874.43 | 5.25 | 0.00 | | | -5.25 | 1000.00 |
+ | 98 | 2019-04-09 | 98 | 50.00 | 50.00 | | 45.03 | 4829.64 | 5.20 | 0.00 | | | -5.20 | 1000.00 |
+ | 99 | 2019-04-10 | 99 | 50.00 | 50.00 | | 44.99 | 4784.79 | 5.16 | 0.00 | | | -5.16 | 1000.00 |
+ | 100 | 2019-04-11 | 100 | 50.00 | 50.00 | | 44.94 | 4739.90 | 5.11 | 0.00 | | | -5.11 | 1000.00 |
+ | 101 | 2019-04-12 | 101 | 50.00 | 50.00 | | 44.89 | 4694.96 | 5.06 | 0.00 | | | -5.06 | 1000.00 |
+ | 102 | 2019-04-13 | 102 | 50.00 | 50.00 | | 44.84 | 4649.98 | 5.01 | 0.00 | | | -5.01 | 1000.00 |
+ | 103 | 2019-04-14 | 103 | 50.00 | 50.00 | | 44.80 | 4604.94 | 4.97 | 0.00 | | | -4.97 | 1000.00 |
+ | 104 | 2019-04-15 | 104 | 50.00 | 50.00 | | 44.75 | 4559.86 | 4.92 | 0.00 | | | -4.92 | 1000.00 |
+ | 105 | 2019-04-16 | 105 | 50.00 | 50.00 | | 44.70 | 4514.73 | 4.87 | 0.00 | | | -4.87 | 1000.00 |
+ | 106 | 2019-04-17 | 106 | 50.00 | 50.00 | | 44.65 | 4469.55 | 4.82 | 0.00 | | | -4.82 | 1000.00 |
+ | 107 | 2019-04-18 | 107 | 50.00 | 50.00 | | 44.60 | 4424.32 | 4.77 | 0.00 | | | -4.77 | 1000.00 |
+ | 108 | 2019-04-19 | 108 | 50.00 | 50.00 | | 44.56 | 4379.05 | 4.72 | 0.00 | | | -4.72 | 1000.00 |
+ | 109 | 2019-04-20 | 109 | 50.00 | 50.00 | | 44.51 | 4333.72 | 4.68 | 0.00 | | | -4.68 | 1000.00 |
+ | 110 | 2019-04-21 | 110 | 50.00 | 50.00 | | 44.46 | 4288.35 | 4.63 | 0.00 | | | -4.63 | 1000.00 |
+ | 111 | 2019-04-22 | 111 | 50.00 | 50.00 | | 44.41 | 4242.93 | 4.58 | 0.00 | | | -4.58 | 1000.00 |
+ | 112 | 2019-04-23 | 112 | 50.00 | 50.00 | | 44.37 | 4197.46 | 4.53 | 0.00 | | | -4.53 | 1000.00 |
+ | 113 | 2019-04-24 | 113 | 50.00 | 50.00 | | 44.32 | 4151.94 | 4.48 | 0.00 | | | -4.48 | 1000.00 |
+ | 114 | 2019-04-25 | 114 | 50.00 | 50.00 | | 44.27 | 4106.38 | 4.43 | 0.00 | | | -4.43 | 1000.00 |
+ | 115 | 2019-04-26 | 115 | 50.00 | 50.00 | | 44.22 | 4060.76 | 4.38 | 0.00 | | | -4.38 | 1000.00 |
+ | 116 | 2019-04-27 | 116 | 50.00 | 50.00 | | 44.18 | 4015.10 | 4.34 | 0.00 | | | -4.34 | 1000.00 |
+ | 117 | 2019-04-28 | 117 | 50.00 | 50.00 | | 44.13 | 3969.38 | 4.29 | 0.00 | | | -4.29 | 1000.00 |
+ | 118 | 2019-04-29 | 118 | 50.00 | 50.00 | | 44.08 | 3923.62 | 4.24 | 0.00 | | | -4.24 | 1000.00 |
+ | 119 | 2019-04-30 | 119 | 50.00 | 50.00 | | 44.04 | 3877.81 | 4.19 | 0.00 | | | -4.19 | 1000.00 |
+ | 120 | 2019-05-01 | 120 | 50.00 | 50.00 | | 43.99 | 3831.95 | 4.14 | 0.00 | | | -4.14 | 1000.00 |
+ | 121 | 2019-05-02 | 121 | 50.00 | 50.00 | | 43.94 | 3786.04 | 4.09 | 0.00 | | | -4.09 | 1000.00 |
+ | 122 | 2019-05-03 | 122 | 50.00 | 50.00 | | 43.90 | 3740.09 | 4.04 | 0.00 | | | -4.04 | 1000.00 |
+ | 123 | 2019-05-04 | 123 | 50.00 | 50.00 | | 43.85 | 3694.08 | 3.99 | 0.00 | | | -3.99 | 1000.00 |
+ | 124 | 2019-05-05 | 124 | 50.00 | 50.00 | | 43.80 | 3648.03 | 3.94 | 0.00 | | | -3.94 | 1000.00 |
+ | 125 | 2019-05-06 | 125 | 50.00 | 50.00 | | 43.76 | 3601.92 | 3.90 | 0.00 | | | -3.90 | 1000.00 |
+ | 126 | 2019-05-07 | 126 | 50.00 | 50.00 | | 43.71 | 3555.77 | 3.85 | 0.00 | | | -3.85 | 1000.00 |
+ | 127 | 2019-05-08 | 127 | 50.00 | 50.00 | | 43.66 | 3509.56 | 3.80 | 0.00 | | | -3.80 | 1000.00 |
+ | 128 | 2019-05-09 | 128 | 50.00 | 50.00 | | 43.62 | 3463.31 | 3.75 | 0.00 | | | -3.75 | 1000.00 |
+ | 129 | 2019-05-10 | 129 | 50.00 | 50.00 | | 43.57 | 3417.01 | 3.70 | 0.00 | | | -3.70 | 1000.00 |
+ | 130 | 2019-05-11 | 130 | 50.00 | 50.00 | | 43.52 | 3370.66 | 3.65 | 0.00 | | | -3.65 | 1000.00 |
+ | 131 | 2019-05-12 | 131 | 50.00 | 50.00 | | 43.48 | 3324.26 | 3.60 | 0.00 | | | -3.60 | 1000.00 |
+ | 132 | 2019-05-13 | 132 | 50.00 | 50.00 | | 43.43 | 3277.81 | 3.55 | 0.00 | | | -3.55 | 1000.00 |
+ | 133 | 2019-05-14 | 133 | 50.00 | 50.00 | | 43.38 | 3231.31 | 3.50 | 0.00 | | | -3.50 | 1000.00 |
+ | 134 | 2019-05-15 | 134 | 50.00 | 50.00 | | 43.34 | 3184.76 | 3.45 | 0.00 | | | -3.45 | 1000.00 |
+ | 135 | 2019-05-16 | 135 | 50.00 | 50.00 | | 43.29 | 3138.16 | 3.40 | 0.00 | | | -3.40 | 1000.00 |
+ | 136 | 2019-05-17 | 136 | 50.00 | 50.00 | | 43.24 | 3091.51 | 3.35 | 0.00 | | | -3.35 | 1000.00 |
+ | 137 | 2019-05-18 | 137 | 50.00 | 50.00 | | 43.20 | 3044.81 | 3.30 | 0.00 | | | -3.30 | 1000.00 |
+ | 138 | 2019-05-19 | 138 | 50.00 | 50.00 | | 43.15 | 2998.06 | 3.25 | 0.00 | | | -3.25 | 1000.00 |
+ | 139 | 2019-05-20 | 139 | 50.00 | 50.00 | | 43.11 | 2951.26 | 3.20 | 0.00 | | | -3.20 | 1000.00 |
+ | 140 | 2019-05-21 | 140 | 50.00 | 50.00 | | 43.06 | 2904.42 | 3.15 | 0.00 | | | -3.15 | 1000.00 |
+ | 141 | 2019-05-22 | 141 | 50.00 | 50.00 | | 43.01 | 2857.52 | 3.10 | 0.00 | | | -3.10 | 1000.00 |
+ | 142 | 2019-05-23 | 142 | 50.00 | 50.00 | | 42.97 | 2810.57 | 3.05 | 0.00 | | | -3.05 | 1000.00 |
+ | 143 | 2019-05-24 | 143 | 50.00 | 50.00 | | 42.92 | 2763.57 | 3.00 | 0.00 | | | -3.00 | 1000.00 |
+ | 144 | 2019-05-25 | 144 | 50.00 | 50.00 | | 42.88 | 2716.52 | 2.95 | 0.00 | | | -2.95 | 1000.00 |
+ | 145 | 2019-05-26 | 145 | 50.00 | 50.00 | | 42.83 | 2669.42 | 2.90 | 0.00 | | | -2.90 | 1000.00 |
+ | 146 | 2019-05-27 | 146 | 50.00 | 50.00 | | 42.79 | 2622.27 | 2.85 | 0.00 | | | -2.85 | 1000.00 |
+ | 147 | 2019-05-28 | 147 | 50.00 | 50.00 | | 42.74 | 2575.07 | 2.80 | 0.00 | | | -2.80 | 1000.00 |
+ | 148 | 2019-05-29 | 148 | 50.00 | 50.00 | | 42.69 | 2527.82 | 2.75 | 0.00 | | | -2.75 | 1000.00 |
+ | 149 | 2019-05-30 | 149 | 50.00 | 50.00 | | 42.65 | 2480.52 | 2.70 | 0.00 | | | -2.70 | 1000.00 |
+ | 150 | 2019-05-31 | 150 | 50.00 | 50.00 | | 42.60 | 2433.17 | 2.65 | 0.00 | | | -2.65 | 1000.00 |
+ | 151 | 2019-06-01 | 151 | 50.00 | 50.00 | | 42.56 | 2385.77 | 2.60 | 0.00 | | | -2.60 | 1000.00 |
+ | 152 | 2019-06-02 | 152 | 50.00 | 50.00 | | 42.51 | 2338.31 | 2.55 | 0.00 | | | -2.55 | 1000.00 |
+ | 153 | 2019-06-03 | 153 | 50.00 | 50.00 | | 42.47 | 2290.81 | 2.50 | 0.00 | | | -2.50 | 1000.00 |
+ | 154 | 2019-06-04 | 154 | 50.00 | 50.00 | | 42.42 | 2243.26 | 2.45 | 0.00 | | | -2.45 | 1000.00 |
+ | 155 | 2019-06-05 | 155 | 50.00 | 50.00 | | 42.38 | 2195.65 | 2.40 | 0.00 | | | -2.40 | 1000.00 |
+ | 156 | 2019-06-06 | 156 | 50.00 | 50.00 | | 42.33 | 2148.00 | 2.34 | 0.00 | | | -2.34 | 1000.00 |
+ | 157 | 2019-06-07 | 157 | 50.00 | 50.00 | | 42.29 | 2100.29 | 2.29 | 0.00 | | | -2.29 | 1000.00 |
+ | 158 | 2019-06-08 | 158 | 50.00 | 50.00 | | 42.24 | 2052.53 | 2.24 | 0.00 | | | -2.24 | 1000.00 |
+ | 159 | 2019-06-09 | 159 | 50.00 | 50.00 | | 42.20 | 2004.73 | 2.19 | 0.00 | | | -2.19 | 1000.00 |
+ | 160 | 2019-06-10 | 160 | 50.00 | 50.00 | | 42.15 | 1956.87 | 2.14 | 0.00 | | | -2.14 | 1000.00 |
+ | 161 | 2019-06-11 | 161 | 50.00 | 50.00 | | 42.11 | 1908.96 | 2.09 | 0.00 | | | -2.09 | 1000.00 |
+ | 162 | 2019-06-12 | 162 | 50.00 | 50.00 | | 42.06 | 1860.99 | 2.04 | 0.00 | | | -2.04 | 1000.00 |
+ | 163 | 2019-06-13 | 163 | 50.00 | 50.00 | | 42.02 | 1812.98 | 1.99 | 0.00 | | | -1.99 | 1000.00 |
+ | 164 | 2019-06-14 | 164 | 50.00 | 50.00 | | 41.97 | 1764.92 | 1.94 | 0.00 | | | -1.94 | 1000.00 |
+ | 165 | 2019-06-15 | 165 | 50.00 | 50.00 | | 41.93 | 1716.80 | 1.88 | 0.00 | | | -1.88 | 1000.00 |
+ | 166 | 2019-06-16 | 166 | 50.00 | 50.00 | | 41.88 | 1668.64 | 1.83 | 0.00 | | | -1.83 | 1000.00 |
+ | 167 | 2019-06-17 | 167 | 50.00 | 50.00 | | 41.84 | 1620.42 | 1.78 | 0.00 | | | -1.78 | 1000.00 |
+ | 168 | 2019-06-18 | 168 | 50.00 | 50.00 | | 41.79 | 1572.15 | 1.73 | 0.00 | | | -1.73 | 1000.00 |
+ | 169 | 2019-06-19 | 169 | 50.00 | 50.00 | | 41.75 | 1523.83 | 1.68 | 0.00 | | | -1.68 | 1000.00 |
+ | 170 | 2019-06-20 | 170 | 50.00 | 50.00 | | 41.70 | 1475.45 | 1.63 | 0.00 | | | -1.63 | 1000.00 |
+ | 171 | 2019-06-21 | 171 | 50.00 | 50.00 | | 41.66 | 1427.03 | 1.58 | 0.00 | | | -1.58 | 1000.00 |
+ | 172 | 2019-06-22 | 172 | 50.00 | 50.00 | | 41.61 | 1378.55 | 1.52 | 0.00 | | | -1.52 | 1000.00 |
+ | 173 | 2019-06-23 | 173 | 50.00 | 50.00 | | 41.57 | 1330.02 | 1.47 | 0.00 | | | -1.47 | 1000.00 |
+ | 174 | 2019-06-24 | 174 | 50.00 | 50.00 | | 41.53 | 1281.45 | 1.42 | 0.00 | | | -1.42 | 1000.00 |
+ | 175 | 2019-06-25 | 175 | 50.00 | 50.00 | | 41.48 | 1232.81 | 1.37 | 0.00 | | | -1.37 | 1000.00 |
+ | 176 | 2019-06-26 | 176 | 50.00 | 50.00 | | 41.44 | 1184.13 | 1.32 | 0.00 | | | -1.32 | 1000.00 |
+ | 177 | 2019-06-27 | 177 | 50.00 | 50.00 | | 41.39 | 1135.39 | 1.26 | 0.00 | | | -1.26 | 1000.00 |
+ | 178 | 2019-06-28 | 178 | 50.00 | 50.00 | | 41.35 | 1086.61 | 1.21 | 0.00 | | | -1.21 | 1000.00 |
+ | 179 | 2019-06-29 | 179 | 50.00 | 50.00 | | 41.31 | 1037.77 | 1.16 | 0.00 | | | -1.16 | 1000.00 |
+ | 180 | 2019-06-30 | 180 | 50.00 | 50.00 | | 41.26 | 988.88 | 1.11 | 0.00 | | | -1.11 | 1000.00 |
+ | 181 | 2019-07-01 | 181 | 50.00 | 50.00 | | 41.22 | 939.93 | 1.06 | 0.00 | | | -1.06 | 1000.00 |
+ | 182 | 2019-07-02 | 182 | 50.00 | 50.00 | | 41.17 | 890.93 | 1.00 | 0.00 | | | -1.00 | 1000.00 |
+ | 183 | 2019-07-03 | 183 | 50.00 | 50.00 | | 41.13 | 841.89 | 0.95 | 0.00 | | | -0.95 | 1000.00 |
+ | 184 | 2019-07-04 | 184 | 50.00 | 50.00 | | 41.09 | 792.79 | 0.90 | 0.00 | | | -0.90 | 1000.00 |
+ | 185 | 2019-07-05 | 185 | 50.00 | 50.00 | | 41.04 | 743.63 | 0.85 | 0.00 | | | -0.85 | 1000.00 |
+ | 186 | 2019-07-06 | 186 | 50.00 | 50.00 | | 41.00 | 694.43 | 0.79 | 0.00 | | | -0.79 | 1000.00 |
+ | 187 | 2019-07-07 | 187 | 50.00 | 50.00 | | 40.95 | 645.17 | 0.74 | 0.00 | | | -0.74 | 1000.00 |
+ | 188 | 2019-07-08 | 188 | 50.00 | 50.00 | | 40.91 | 595.86 | 0.69 | 0.00 | | | -0.69 | 1000.00 |
+ | 189 | 2019-07-09 | 189 | 50.00 | 50.00 | | 40.87 | 546.49 | 0.64 | 0.00 | | | -0.64 | 1000.00 |
+ | 190 | 2019-07-10 | 190 | 50.00 | 50.00 | | 40.82 | 497.08 | 0.58 | 0.00 | | | -0.58 | 1000.00 |
+ | 191 | 2019-07-11 | 191 | 50.00 | 50.00 | | 40.78 | 447.61 | 0.53 | 0.00 | | | -0.53 | 1000.00 |
+ | 192 | 2019-07-12 | 192 | 50.00 | 50.00 | | 40.74 | 398.08 | 0.48 | 0.00 | | | -0.48 | 1000.00 |
+ | 193 | 2019-07-13 | 193 | 50.00 | 50.00 | | 40.69 | 348.51 | 0.43 | 0.00 | | | -0.43 | 1000.00 |
+ | 194 | 2019-07-14 | 194 | 50.00 | 50.00 | | 40.65 | 298.88 | 0.37 | 0.00 | | | -0.37 | 1000.00 |
+ | 195 | 2019-07-15 | 195 | 50.00 | 50.00 | | 40.61 | 249.20 | 0.32 | 0.00 | | | -0.32 | 1000.00 |
+ | 196 | 2019-07-16 | 196 | 50.00 | 50.00 | | 40.56 | 199.47 | 0.27 | 0.00 | | | -0.27 | 1000.00 |
+ | 197 | 2019-07-17 | 197 | 50.00 | 50.00 | | 40.52 | 149.68 | 0.21 | 0.00 | | | -0.21 | 1000.00 |
+ | 198 | 2019-07-18 | 198 | 50.00 | 50.00 | | 40.48 | 99.84 | 0.16 | 0.00 | | | -0.16 | 1000.00 |
+ | 199 | 2019-07-19 | 199 | 50.00 | 50.00 | | 40.43 | 49.95 | 0.11 | 0.00 | | | -0.11 | 1000.00 |
+ | 200 | 2019-07-20 | 200 | 50.00 | 50.00 | | 40.39 | 0.00 | 0.05 | 0.00 | | | -0.05 | 1000.00 |
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/InternalWorkingCapitalLoanApiResource.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/InternalWorkingCapitalLoanApiResource.java
new file mode 100644
index 0000000000..ce308c44bf
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/InternalWorkingCapitalLoanApiResource.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.api;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.boot.FineractProfiles;
+import org.apache.fineract.portfolio.workingcapitalloan.data.ProjectedAmortizationScheduleGenerateRequest;
+import org.apache.fineract.portfolio.workingcapitalloan.service.WorkingCapitalLoanAmortizationScheduleWriteService;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@RequiredArgsConstructor
+@Profile(FineractProfiles.TEST)
+@Component
+@Path("v1/internal/working-capital-loans")
+@Tag(name = "Working Capital Loans", description = "Internal WCL testing API. This API should be disabled in production!")
+public class InternalWorkingCapitalLoanApiResource implements InitializingBean {
+
+ private final WorkingCapitalLoanAmortizationScheduleWriteService writeService;
+
+ @Override
+ @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
+ public void afterPropertiesSet() throws Exception {
+ log.warn("------------------------------------------------------------");
+ log.warn(" ");
+ log.warn("DO NOT USE THIS IN PRODUCTION!");
+ log.warn("Internal client services mode is enabled");
+ log.warn("DO NOT USE THIS IN PRODUCTION!");
+ log.warn(" ");
+ log.warn("------------------------------------------------------------");
+ }
+
+ @POST
+ @Path("{loanId}/amortization-schedule")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Generate and save Projected Amortization Schedule (testing)", description = """
+ Generates a projected amortization schedule from the provided parameters \
+ and saves it for the given Working Capital Loan.
+
+ DO NOT USE THIS IN PRODUCTION! In the real flow, the schedule will be \
+ generated during loan approval/disbursement from the loan and product data.""")
+ @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK"),
+ @ApiResponse(responseCode = "404", description = "Working Capital Loan not found") })
+ public void generateAmortizationSchedule(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
+ final ProjectedAmortizationScheduleGenerateRequest request) {
+ writeService.generateAndSaveAmortizationSchedule(loanId, request);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanAmortizationScheduleApiResource.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanAmortizationScheduleApiResource.java
new file mode 100644
index 0000000000..cc91ad6395
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/api/WorkingCapitalLoanAmortizationScheduleApiResource.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.api;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.portfolio.workingcapitalloan.data.ProjectedAmortizationScheduleData;
+import org.apache.fineract.portfolio.workingcapitalloan.service.WorkingCapitalLoanAmortizationScheduleReadService;
+import org.springframework.stereotype.Component;
+
+@Path("/v1/working-capital-loans")
+@Component
+@Tag(name = "Working Capital Loans", description = "Working Capital Loan operations including projected amortization schedule.")
+@RequiredArgsConstructor
+public class WorkingCapitalLoanAmortizationScheduleApiResource {
+
+ private final WorkingCapitalLoanAmortizationScheduleReadService readService;
+
+ @GET
+ @Path("{loanId}/amortization-schedule")
+ @Consumes({ MediaType.APPLICATION_JSON })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Operation(summary = "Retrieve Projected Amortization Schedule", description = """
+ Returns the projected amortization schedule for a Working Capital Loan.
+
+ The schedule contains per-payment details including expected and forecast payments, \
+ discount factors, NPV values, balances, expected and actual amortization amounts, \
+ income modifications, and deferred balance.
+
+ Example Request:
+
+ working-capital-loans/1/amortization-schedule""")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = ProjectedAmortizationScheduleData.class))),
+ @ApiResponse(responseCode = "404", description = "Working Capital Loan or schedule not found") })
+ public ProjectedAmortizationScheduleData retrieveAmortizationSchedule(
+ @PathParam("loanId") @Parameter(description = "loanId") final Long loanId) {
+ return readService.retrieveAmortizationSchedule(loanId);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/DefaultProjectedAmortizationScheduleCalculator.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/DefaultProjectedAmortizationScheduleCalculator.java
new file mode 100644
index 0000000000..c639f69760
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/DefaultProjectedAmortizationScheduleCalculator.java
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.calc;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.time.LocalDate;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.springframework.lang.NonNull;
+import org.springframework.stereotype.Component;
+
+/**
+ * Default implementation of {@link ProjectedAmortizationScheduleCalculator}. Delegates to
+ * {@link ProjectedAmortizationScheduleModel} methods.
+ */
+@Component
+public final class DefaultProjectedAmortizationScheduleCalculator implements ProjectedAmortizationScheduleCalculator {
+
+ @Override
+ @NonNull
+ public ProjectedAmortizationScheduleModel generateModel(@NonNull final BigDecimal originationFeeAmount,
+ @NonNull final BigDecimal netDisbursementAmount, @NonNull final BigDecimal totalPaymentValue,
+ @NonNull final BigDecimal periodPaymentRate, final int npvDayCount, @NonNull final LocalDate expectedDisbursementDate,
+ @NonNull final MathContext mc, @NonNull final MonetaryCurrency currency) {
+ return ProjectedAmortizationScheduleModel.generate(originationFeeAmount, netDisbursementAmount, totalPaymentValue,
+ periodPaymentRate, npvDayCount, expectedDisbursementDate, mc, currency);
+ }
+
+ @Override
+ @NonNull
+ public ProjectedAmortizationScheduleModel addDisbursement(@NonNull final ProjectedAmortizationScheduleModel model,
+ @NonNull final BigDecimal newDiscountAmount, @NonNull final BigDecimal newNetAmount, @NonNull final LocalDate newStartDate) {
+ return model.regenerate(newDiscountAmount, newNetAmount, newStartDate);
+ }
+
+ @Override
+ public void applyPayment(@NonNull final ProjectedAmortizationScheduleModel model, @NonNull final LocalDate paymentDate,
+ @NonNull final BigDecimal paymentAmount) {
+ model.applyPayment(paymentDate, paymentAmount);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedAmortizationScheduleCalculator.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedAmortizationScheduleCalculator.java
new file mode 100644
index 0000000000..f7e53452aa
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedAmortizationScheduleCalculator.java
@@ -0,0 +1,73 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.calc;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.time.LocalDate;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.springframework.lang.NonNull;
+
+/**
+ * Calculator service for Working Capital loan projected amortization schedule. Analogous to {@code EMICalculator} for
+ * progressive loans, but with a minimal API: generation, disbursement and payment.
+ */
+public interface ProjectedAmortizationScheduleCalculator {
+
+ /**
+ * Creates an initial projected amortization schedule model (at loan creation).
+ *
+ * @return model with no payments applied
+ */
+ @NonNull
+ ProjectedAmortizationScheduleModel generateModel(@NonNull BigDecimal originationFeeAmount, @NonNull BigDecimal netDisbursementAmount,
+ @NonNull BigDecimal totalPaymentValue, @NonNull BigDecimal periodPaymentRate, int npvDayCount,
+ @NonNull LocalDate expectedDisbursementDate, @NonNull MathContext mc, @NonNull MonetaryCurrency currency);
+
+ /**
+ * Recalculates the model with updated financial parameters (at approval or disbursement). Preserves already applied
+ * payments on the model.
+ *
+ * @param model
+ * current model (may have payments)
+ * @param newDiscountAmount
+ * approved or disbursed discount
+ * @param newNetAmount
+ * approved or actual net amount
+ * @param newStartDate
+ * actual start date
+ * @return new model with recalculated schedule
+ */
+ @NonNull
+ ProjectedAmortizationScheduleModel addDisbursement(@NonNull ProjectedAmortizationScheduleModel model,
+ @NonNull BigDecimal newDiscountAmount, @NonNull BigDecimal newNetAmount, @NonNull LocalDate newStartDate);
+
+ /**
+ * Applies a payment to the model. The model is mutated in place; callers can read the updated payment directly from
+ * the model.
+ *
+ * @param model
+ * current model (mutated in place)
+ * @param paymentDate
+ * the date when payment was made
+ * @param paymentAmount
+ * actual payment amount
+ */
+ void applyPayment(@NonNull ProjectedAmortizationScheduleModel model, @NonNull LocalDate paymentDate, @NonNull BigDecimal paymentAmount);
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedAmortizationScheduleModel.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedAmortizationScheduleModel.java
new file mode 100644
index 0000000000..385f04ee1a
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedAmortizationScheduleModel.java
@@ -0,0 +1,430 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.calc;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.experimental.Accessors;
+import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
+import org.apache.fineract.infrastructure.core.service.MathUtil;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+/**
+ * Projected Amortization Schedule model for Working Capital loans.
+ *
+ * <h3>Lifecycle</h3>
+ * <ol>
+ * <li>{@link #generate} — create initial schedule (at loan creation)</li>
+ * <li>{@link #regenerate} — recalculate with new amounts (at approval / disbursement)</li>
+ * <li>{@link #applyPayment} — record payments by date; schedule rebuilds after each</li>
+ * </ol>
+ */
+@Getter
+@Accessors(fluent = true)
+public final class ProjectedAmortizationScheduleModel {
+
+ private static final String MODEL_VERSION = "1";
+
+ private final Money originationFeeAmount;
+ private final Money netDisbursementAmount;
+ private final Money totalPaymentValue;
+ private final BigDecimal periodPaymentRate;
+ private final int npvDayCount;
+ private final LocalDate expectedDisbursementDate;
+
+ /** {@code (TPV × periodPaymentRate) / npvDayCount} — constant across payments. */
+ private final Money expectedPaymentAmount;
+
+ /** {@code roundUp((netDisbursementAmount + originationFeeAmount) / expectedPaymentAmount)} */
+ private final int loanTerm;
+
+ /** Periodic EIR from {@code RATE(loanTerm, -expectedPayment, netDisbursementAmount)}. */
+ private final BigDecimal effectiveInterestRate;
+
+ @JsonExclude
+ private final MathContext mc;
+
+ @JsonExclude
+ private final MonetaryCurrency currency;
+
+ @Getter(AccessLevel.NONE)
+ private final List<AppliedPayment> appliedPayments;
+
+ @Getter(AccessLevel.NONE)
+ private List<ProjectedPayment> payments;
+
+ private ProjectedAmortizationScheduleModel(final Money originationFeeAmount, final Money netDisbursementAmount,
+ final Money totalPaymentValue, final BigDecimal periodPaymentRate, final int npvDayCount,
+ final LocalDate expectedDisbursementDate, final Money expectedPaymentAmount, final int loanTerm,
+ final BigDecimal effectiveInterestRate, final MathContext mc, final MonetaryCurrency currency) {
+ this.originationFeeAmount = originationFeeAmount;
+ this.netDisbursementAmount = netDisbursementAmount;
+ this.totalPaymentValue = totalPaymentValue;
+ this.periodPaymentRate = periodPaymentRate;
+ this.npvDayCount = npvDayCount;
+ this.expectedDisbursementDate = expectedDisbursementDate;
+ this.expectedPaymentAmount = expectedPaymentAmount;
+ this.loanTerm = loanTerm;
+ this.effectiveInterestRate = effectiveInterestRate;
+ this.mc = mc;
+ this.currency = currency;
+ this.appliedPayments = new ArrayList<>();
+ rebuildPayments();
+ }
+
+ /**
+ * Creates a skeleton instance for Gson deserialization. Gson will overwrite final fields via reflection; payments
+ * are restored from JSON directly (no rebuild needed).
+ */
+ public static ProjectedAmortizationScheduleModel forDeserialization(final MathContext mc, final MonetaryCurrency currency) {
+ return new ProjectedAmortizationScheduleModel(mc, currency);
+ }
+
+ private ProjectedAmortizationScheduleModel(final MathContext mc, final MonetaryCurrency currency) {
+ this.originationFeeAmount = null;
+ this.netDisbursementAmount = null;
+ this.totalPaymentValue = null;
+ this.periodPaymentRate = null;
+ this.npvDayCount = 0;
+ this.expectedDisbursementDate = null;
+ this.expectedPaymentAmount = null;
+ this.loanTerm = 0;
+ this.effectiveInterestRate = null;
+ this.mc = mc;
+ this.currency = currency;
+ this.appliedPayments = new ArrayList<>();
+ this.payments = List.of();
+ }
+
+ public List<ProjectedPayment> payments() {
+ return payments;
+ }
+
+ public static ProjectedAmortizationScheduleModel generate(final BigDecimal originationFeeAmount, final BigDecimal netDisbursementAmount,
+ final BigDecimal totalPaymentValue, final BigDecimal periodPaymentRate, final int npvDayCount,
+ final LocalDate expectedDisbursementDate, final MathContext mc, final MonetaryCurrency currency) {
+
+ Objects.requireNonNull(originationFeeAmount, "originationFeeAmount");
+ Objects.requireNonNull(netDisbursementAmount, "netDisbursementAmount");
+ Objects.requireNonNull(totalPaymentValue, "totalPaymentValue");
+ Objects.requireNonNull(periodPaymentRate, "periodPaymentRate");
+ Objects.requireNonNull(expectedDisbursementDate, "expectedDisbursementDate");
+ Objects.requireNonNull(currency, "currency");
+ if (netDisbursementAmount.signum() <= 0) {
+ throw new IllegalArgumentException("netDisbursementAmount must be positive");
+ }
+ if (npvDayCount <= 0) {
+ throw new IllegalArgumentException("npvDayCount must be positive");
+ }
+
+ final BigDecimal expectedPayment = totalPaymentValue.multiply(periodPaymentRate, mc).divide(BigDecimal.valueOf(npvDayCount), mc);
+ if (expectedPayment.signum() <= 0) {
+ throw new IllegalArgumentException("expectedPaymentAmount must be positive (check totalPaymentValue and periodPaymentRate)");
+ }
+
+ final int term = netDisbursementAmount.add(originationFeeAmount, mc).divide(expectedPayment, mc).setScale(0, RoundingMode.UP)
+ .intValueExact();
+ if (term <= 0) {
+ throw new IllegalArgumentException("computed loan term must be positive, got: " + term);
+ }
+
+ final BigDecimal eir = TvmFunctions.rate(term, expectedPayment.negate(), netDisbursementAmount, mc);
+
+ return new ProjectedAmortizationScheduleModel(Money.of(currency, originationFeeAmount, mc),
+ Money.of(currency, netDisbursementAmount, mc), Money.of(currency, totalPaymentValue, mc), periodPaymentRate, npvDayCount,
+ expectedDisbursementDate, Money.of(currency, expectedPayment, mc), term, eir, mc, currency);
+ }
+
+ public void applyPayment(final LocalDate paymentDate, final BigDecimal amount) {
+ Objects.requireNonNull(paymentDate, "paymentDate");
+ Objects.requireNonNull(amount, "amount");
+ final int index = resolvePaymentIndex(paymentDate);
+ if (index < 0 || index >= loanTerm) {
+ throw new IllegalArgumentException("paymentDate " + paymentDate + " is outside the valid range ["
+ + expectedDisbursementDate.plusDays(1) + " .. " + expectedDisbursementDate.plusDays(loanTerm) + "]");
+ }
+ appliedPayments.add(new AppliedPayment(paymentDate, amount));
+ rebuildPayments();
+ }
+
+ /** Creates a new model with updated parameters, preserving applied payments. */
+ public ProjectedAmortizationScheduleModel regenerate(final BigDecimal newDiscountAmount, final BigDecimal newNetAmount,
+ final LocalDate newStartDate) {
+ final ProjectedAmortizationScheduleModel newModel = generate(newDiscountAmount, newNetAmount, totalPaymentValue.getAmount(),
+ periodPaymentRate, npvDayCount, newStartDate, mc, currency);
+ newModel.appliedPayments.addAll(appliedPayments);
+ newModel.rebuildPayments();
+ return newModel;
+ }
+
+ private void rebuildPayments() {
+ final Map<LocalDate, BigDecimal> paymentsByDate = aggregatePaymentsByDate();
+ final List<BigDecimal> paymentList = buildPaymentList(paymentsByDate);
+ this.payments = List.copyOf(buildPayments(paymentList, paymentsByDate.size()));
+ }
+
+ private Map<LocalDate, BigDecimal> aggregatePaymentsByDate() {
+ final Map<LocalDate, BigDecimal> result = new HashMap<>();
+ for (final AppliedPayment payment : appliedPayments) {
+ result.merge(payment.date(), payment.amount(), BigDecimal::add);
+ }
+ return result;
+ }
+
+ private List<BigDecimal> buildPaymentList(final Map<LocalDate, BigDecimal> paymentsByDate) {
+ final List<BigDecimal> result = new ArrayList<>(loanTerm);
+ for (int i = 0; i < loanTerm; i++) {
+ final LocalDate paymentDate = expectedDisbursementDate.plusDays(i + 1);
+ result.add(paymentsByDate.get(paymentDate));
+ }
+ return result;
+ }
+
+ private int resolvePaymentIndex(final LocalDate date) {
+ return (int) ChronoUnit.DAYS.between(expectedDisbursementDate, date) - 1;
+ }
+
+ private List<ProjectedPayment> buildPayments(final List<BigDecimal> payments, final int appliedCount) {
+ final BalancesAndAmortizations ba = computeBalancesAndAmortizations();
+ final PaymentAnalysis pa = analyzePayments(payments, appliedCount);
+ final List<BigDecimal> actualAmortizations = computeActualAmortizations(ba.expectedAmortizations, payments, appliedCount);
+ final List<BigDecimal> runningExpected = computeRunningExpectedPayments(pa.excess);
+ final List<ProjectedPayment> tailPayments = new ArrayList<>();
+ final BigDecimal tailNpv = buildTailPeriodsAndComputeNpv(tailPayments, pa.shortfall, appliedCount);
+ final BigDecimal totalNetAmortization = computeTotalNetAmortization(payments, runningExpected, appliedCount, tailNpv);
+
+ final BigDecimal originationFee = originationFeeAmount.getAmount();
+ final BigDecimal safeExpectedPayment = MathUtil.negativeToZero(expectedPaymentAmount.getAmount());
+
+ final List<ProjectedPayment> result = new ArrayList<>(loanTerm + 2 + tailPayments.size());
+ result.add(createDisbursementPayment(appliedCount));
+
+ BigDecimal cumulativeActualAmort = BigDecimal.ZERO;
+ for (int i = 0; i < loanTerm; i++) {
+ final int periodNo = i + 1;
+ final boolean hasAppliedAmount = payments.get(i) != null;
+ final long count = (long) loanTerm + appliedCount - periodNo;
+ final long paymentsLeft = paymentsLeft(periodNo, appliedCount);
+ final BigDecimal safeDf = safeDiscountFactor(paymentsLeft);
+ final BigDecimal safeRunningExpected = MathUtil.negativeToZero(runningExpected.get(i));
+ final BigDecimal npvSource = hasAppliedAmount ? payments.get(i) : safeRunningExpected;
+ final BigDecimal npvValue = MathUtil.negativeToZero(npvSource.multiply(safeDf, mc));
+ final BigDecimal safeExpectedAmort = ba.expectedAmortizations.get(i).min(originationFee);
+
+ final BigDecimal netAmortization;
+ final BigDecimal actualAmortization;
+ final BigDecimal incomeModification;
+
+ if (hasAppliedAmount) {
+ actualAmortization = actualAmortizations.get(i);
+ netAmortization = totalNetAmortization.subtract(cumulativeActualAmort, mc).min(originationFee);
+ cumulativeActualAmort = cumulativeActualAmort.add(actualAmortization, mc).min(originationFee);
+ incomeModification = actualAmortization.subtract(safeExpectedAmort, mc);
+ } else {
+ netAmortization = BigDecimal.ZERO;
+ actualAmortization = null;
+ incomeModification = safeExpectedAmort.negate();
+ }
+
+ final BigDecimal deferredBalance = originationFee.subtract(cumulativeActualAmort, mc);
+ final BigDecimal balance = ba.balances.get(i);
+ result.add(new ProjectedPayment(periodNo, expectedDisbursementDate.plusDays(periodNo), count, paymentsLeft,
+ money(safeExpectedPayment), money(safeRunningExpected), safeDf, money(npvValue), money(balance),
+ money(safeExpectedAmort), money(netAmortization), hasAppliedAmount ? money(payments.get(i)) : null,
+ actualAmortization != null ? money(actualAmortization) : null, money(incomeModification), money(deferredBalance)));
+ }
+
+ result.addAll(tailPayments);
+
+ while (result.size() > 1) {
+ final ProjectedPayment last = result.getLast();
+ if (last.forecastPaymentAmount() != null && last.forecastPaymentAmount().isZero()) {
+ result.removeLast();
+ } else {
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ private ProjectedPayment createDisbursementPayment(final int appliedCount) {
+ final Money negDisbursement = netDisbursementAmount.negated(mc);
+ final long count = (long) loanTerm + appliedCount;
+ return new ProjectedPayment(0, expectedDisbursementDate, count, 0L, negDisbursement, null, BigDecimal.ONE, negDisbursement,
+ netDisbursementAmount, null, null, null, null, null, originationFeeAmount);
+ }
+
+ /**
+ * {@code balance[i] = balance[i-1]×(1+EIR) - expectedPayment}<br>
+ * {@code expectedAmort[i] = balance[i] + expectedPayment - balance[i-1]}
+ */
+ private BalancesAndAmortizations computeBalancesAndAmortizations() {
+ final BigDecimal onePlusRate = BigDecimal.ONE.add(effectiveInterestRate, mc);
+ final BigDecimal expectedPayment = expectedPaymentAmount.getAmount();
+ final List<BigDecimal> balances = new ArrayList<>(loanTerm);
+ final List<BigDecimal> expectedAmortizations = new ArrayList<>(loanTerm);
+ BigDecimal prevBalance = netDisbursementAmount.getAmount();
+ for (int i = 0; i < loanTerm; i++) {
+ final BigDecimal balance = prevBalance.multiply(onePlusRate, mc).subtract(expectedPayment, mc);
+ balances.add(balance);
+ expectedAmortizations.add(balance.add(expectedPayment, mc).subtract(prevBalance, mc));
+ prevBalance = balance;
+ }
+ return new BalancesAndAmortizations(balances, expectedAmortizations);
+ }
+
+ private PaymentAnalysis analyzePayments(final List<BigDecimal> payments, final int appliedCount) {
+ final BigDecimal expectedPayment = expectedPaymentAmount.getAmount();
+ BigDecimal shortfall = BigDecimal.ZERO;
+ BigDecimal excess = BigDecimal.ZERO;
+ for (int i = 0; i < appliedCount; i++) {
+ final BigDecimal diff = payments.get(i).subtract(expectedPayment, mc);
+ if (diff.signum() > 0) {
+ excess = excess.add(diff, mc);
+ } else if (diff.signum() < 0) {
+ shortfall = shortfall.add(diff.negate(), mc);
+ }
+ }
+ return new PaymentAnalysis(shortfall, excess);
+ }
+
+ /** Cursor-based: each payment consumes {@code actualPayment/expectedPayment} periods of expected amortization. */
+ private List<BigDecimal> computeActualAmortizations(final List<BigDecimal> expectedAmortizations, final List<BigDecimal> payments,
+ final int appliedCount) {
+ final BigDecimal expectedPayment = expectedPaymentAmount.getAmount();
+ final List<BigDecimal> result = new ArrayList<>(appliedCount);
+ BigDecimal cursor = BigDecimal.ZERO;
+ for (int i = 0; i < appliedCount; i++) {
+ final BigDecimal periodsConsumed = payments.get(i).divide(expectedPayment, mc);
+ result.add(consumeExpectedAmortization(expectedAmortizations, cursor, periodsConsumed));
+ cursor = cursor.add(periodsConsumed, mc);
+ }
+ return result;
+ }
+
+ private BigDecimal consumeExpectedAmortization(final List<BigDecimal> expectedAmortizations, final BigDecimal startPos,
+ final BigDecimal count) {
+ if (count.signum() <= 0) {
+ return BigDecimal.ZERO;
+ }
+ BigDecimal sum = BigDecimal.ZERO;
+ BigDecimal pos = startPos;
+ BigDecimal remaining = count;
+ while (remaining.signum() > 0 && pos.intValue() < expectedAmortizations.size()) {
+ final int periodIndex = pos.intValue();
+ final BigDecimal posInPeriod = pos.subtract(BigDecimal.valueOf(periodIndex), mc);
+ final BigDecimal availableInPeriod = BigDecimal.ONE.subtract(posInPeriod, mc);
+ final BigDecimal toConsume = remaining.min(availableInPeriod);
+ sum = sum.add(toConsume.multiply(expectedAmortizations.get(periodIndex), mc), mc);
+ pos = pos.add(toConsume, mc);
+ remaining = remaining.subtract(toConsume, mc);
+ }
+ return sum;
+ }
+
+ private List<BigDecimal> computeRunningExpectedPayments(final BigDecimal excess) {
+ final BigDecimal expectedPayment = expectedPaymentAmount.getAmount();
+ final List<BigDecimal> running = new ArrayList<>(loanTerm);
+ for (int i = 0; i < loanTerm; i++) {
+ running.add(expectedPayment);
+ }
+
+ BigDecimal remainingExcess = excess;
+ for (int i = loanTerm - 1; i >= 0 && remainingExcess.signum() > 0; i--) {
+ final BigDecimal reduction = remainingExcess.min(running.get(i));
+ running.set(i, running.get(i).subtract(reduction, mc));
+ remainingExcess = remainingExcess.subtract(reduction, mc);
+ }
+ return running;
+ }
+
+ private BigDecimal buildTailPeriodsAndComputeNpv(final List<ProjectedPayment> tailPayments, final BigDecimal shortfall,
+ final int appliedCount) {
+ final BigDecimal expectedPayment = expectedPaymentAmount.getAmount();
+ BigDecimal tailNpv = BigDecimal.ZERO;
+ BigDecimal remaining = shortfall;
+ int tailIndex = 0;
+ while (remaining.signum() > 0) {
+ final int periodNo = loanTerm + tailIndex + 1;
+ final long dl = paymentsLeft(periodNo, appliedCount);
+ final BigDecimal df = safeDiscountFactor(dl);
+ final BigDecimal forecast = remaining.min(expectedPayment);
+ final BigDecimal npv = MathUtil.negativeToZero(forecast.multiply(df, mc));
+
+ final long count = (long) loanTerm + appliedCount - periodNo;
+ tailNpv = tailNpv.add(npv, mc);
+ tailPayments.add(new ProjectedPayment(periodNo, expectedDisbursementDate.plusDays(periodNo), count, dl, null, money(forecast),
+ df, money(npv), null, null, money(BigDecimal.ZERO), null, null, null, null));
+
+ remaining = remaining.subtract(forecast, mc);
+ tailIndex++;
+ }
+ return tailNpv;
+ }
+
+ /** {@code totalNetAmortization = -netDisbursementAmount + sum(npvSource × DF) + tailNpv} */
+ private BigDecimal computeTotalNetAmortization(final List<BigDecimal> payments, final List<BigDecimal> runningExpected,
+ final int appliedCount, final BigDecimal tailNpv) {
+ BigDecimal total = netDisbursementAmount.getAmount().negate();
+ for (int i = 0; i < loanTerm; i++) {
+ final BigDecimal npvSource = payments.get(i) != null ? payments.get(i) : runningExpected.get(i);
+ final BigDecimal df = safeDiscountFactor(paymentsLeft(i + 1, appliedCount));
+ total = total.add(npvSource.multiply(df, mc), mc);
+ }
+ return total.add(tailNpv, mc);
+ }
+
+ private BigDecimal safeDiscountFactor(final long paymentsLeft) {
+ final BigDecimal df = TvmFunctions.discountFactor(effectiveInterestRate, paymentsLeft, mc);
+ return df.signum() <= 0 ? BigDecimal.ONE : df;
+ }
+
+ private long paymentsLeft(final int periodNumber, final int appliedCount) {
+ return Math.max(0L, (long) periodNumber - appliedCount);
+ }
+
+ private Money money(final BigDecimal amount) {
+ return Money.of(currency, amount, mc);
+ }
+
+ private record BalancesAndAmortizations(List<BigDecimal> balances, List<BigDecimal> expectedAmortizations) {
+ }
+
+ private record PaymentAnalysis(BigDecimal shortfall, BigDecimal excess) {
+ }
+
+ public record AppliedPayment(LocalDate date, BigDecimal amount) {
+ }
+
+ public static String getModelVersion() {
+ return MODEL_VERSION;
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedPayment.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedPayment.java
new file mode 100644
index 0000000000..fe4f3def7c
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedPayment.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.calc;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.experimental.Accessors;
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+/**
+ * Single payment of a Working Capital loan's projected amortization schedule.
+ */
+@Getter
+@Accessors(fluent = true)
+@AllArgsConstructor
+public class ProjectedPayment {
+
+ /** 1-based payment number (0 = disbursement row). */
+ private final int paymentNo;
+
+ private final LocalDate date;
+
+ /** Remaining periods for calculation: {@code loanTerm + appliedPaymentCount - paymentNo}. */
+ private final long count;
+
+ /** Exponent for discount factor: {@code DF = 1/(1+EIR)^paymentsLeft}. Zero for paid periods. */
+ private final long paymentsLeft;
+
+ /** {@code (TPV × periodRate) / dayCount}; negated disbursement for row 0. */
+ private final Money expectedPaymentAmount;
+
+ /**
+ * Running expected payment: always the expected amount, adjusted for excess from the tail; {@code null} for row 0.
+ */
+ private final Money forecastPaymentAmount;
+
+ /** {@code 1 / (1 + EIR)^paymentsLeft} */
+ private final BigDecimal discountFactor;
+
+ /** {@code forecastPayment × discountFactor} */
+ private final Money npvValue;
+
+ /** {@code balance[i] = balance[i-1] × (1+EIR) - expectedPayment} */
+ private final Money balance;
+
+ /** {@code balance[i] + expectedPayment - balance[i-1]} (equivalent to {@code prevBalance × EIR}) */
+ private final Money expectedAmortizationAmount;
+
+ /** First paid: sum of all NPV values; subsequent: {@code netAmort[i-1] - actualAmort[i-1]}. */
+ private final Money netAmortizationAmount;
+
+ private final Money actualPaymentAmount;
+
+ /** Cursor-based consumption of expected amortization proportional to payment ratio. */
+ private final Money actualAmortizationAmount;
+
+ /** {@code actualAmortization - expectedAmortization} */
+ private final Money incomeModification;
+
+ /** {@code deferredBalance[i-1] - actualAmortization[i]} */
+ private final Money deferredBalance;
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/TvmFunctions.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/TvmFunctions.java
new file mode 100644
index 0000000000..34d8a0e18b
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/calc/TvmFunctions.java
@@ -0,0 +1,157 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.calc;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+
+/**
+ * Time Value of Money (TVM) utility functions for working capital loan calculations.
+ *
+ * <ul>
+ * <li>{@link #rate} — periodic interest rate via Newton-Raphson (Excel RATE equivalent)</li>
+ * <li>{@link #discountFactor} — present value discount factor: {@code 1 / (1 + r)^days}</li>
+ * </ul>
+ */
+public final class TvmFunctions {
+
+ private static final int MAX_ITERATIONS = 100;
+ private static final BigDecimal TOLERANCE = new BigDecimal("1E-10");
+ private static final BigDecimal DEFAULT_GUESS = new BigDecimal("0.01");
+ private static final BigDecimal TWO = BigDecimal.valueOf(2);
+
+ private TvmFunctions() {}
+
+ /**
+ * Solves for the periodic interest rate in the present-value annuity equation. Equivalent to Excel
+ * {@code RATE(nper, pmt, pv)} with {@code fv=0, type=0}.
+ *
+ * <p>
+ * Finds {@code r} satisfying: {@code pv·(1+r)^n + pmt·((1+r)^n − 1)/r = 0}
+ *
+ * <p>
+ * Initial guess is derived via linear approximation: {@code r ≈ 2·(pmt·n + pv) / (pv·n)}
+ *
+ * @param nper
+ * number of periods (must be positive)
+ * @param pmt
+ * payment per period (negative = outgoing cash flow)
+ * @param pv
+ * present value (positive = loan disbursement)
+ * @param mc
+ * math context for precision
+ * @return the periodic interest rate
+ * @throws IllegalArgumentException
+ * if nper <= 0
+ * @throws IllegalStateException
+ * if Newton-Raphson does not converge
+ */
+ public static BigDecimal rate(final int nper, final BigDecimal pmt, final BigDecimal pv, final MathContext mc) {
+ return rate(nper, pmt, pv, estimateInitialGuess(nper, pmt, pv, mc), mc);
+ }
+
+ /**
+ * Solves for the periodic interest rate with an explicit initial guess.
+ *
+ * @see #rate(int, BigDecimal, BigDecimal, MathContext)
+ */
+ public static BigDecimal rate(final int nper, final BigDecimal pmt, final BigDecimal pv, final BigDecimal guess, final MathContext mc) {
+ if (nper <= 0) {
+ throw new IllegalArgumentException("nper must be positive, got: " + nper);
+ }
+
+ final BigDecimal n = BigDecimal.valueOf(nper);
+
+ // Zero-rate case: pv + pmt·n ≈ 0
+ if (pv.add(pmt.multiply(n, mc), mc).abs().compareTo(TOLERANCE) < 0) {
+ return BigDecimal.ZERO;
+ }
+
+ BigDecimal r = guess;
+
+ for (int iter = 0; iter < MAX_ITERATIONS; iter++) {
+ if (r.signum() == 0) {
+ r = TOLERANCE; // nudge away from zero to avoid division by zero
+ }
+
+ final BigDecimal onePlusR = BigDecimal.ONE.add(r, mc);
+ final BigDecimal compound = onePlusR.pow(nper, mc); // (1+r)^n
+ final BigDecimal compoundMinusOne = compound.subtract(BigDecimal.ONE, mc); // (1+r)^n − 1
+
+ // f(r) = pv·(1+r)^n + pmt·((1+r)^n − 1) / r
+ final BigDecimal f = pv.multiply(compound, mc).add(pmt.multiply(compoundMinusOne, mc).divide(r, mc));
+
+ // f'(r) = pv·n·(1+r)^(n−1) + pmt·[n·(1+r)^(n−1)·r − ((1+r)^n−1)] / r²
+ final BigDecimal dCompound = n.multiply(onePlusR.pow(nper - 1, mc), mc); // n·(1+r)^(n−1)
+ final BigDecimal rSquared = r.multiply(r, mc);
+ final BigDecimal fPrime = pv.multiply(dCompound, mc)
+ .add(pmt.multiply(dCompound.multiply(r, mc).subtract(compoundMinusOne, mc), mc).divide(rSquared, mc));
+
+ if (fPrime.signum() == 0) {
+ throw new IllegalStateException("RATE: zero derivative at iteration " + iter + ", r=" + r);
+ }
+
+ final BigDecimal correction = f.divide(fPrime, mc);
+ r = r.subtract(correction, mc);
+
+ if (correction.abs().compareTo(TOLERANCE) < 0) {
+ return r;
+ }
+ }
+
+ throw new IllegalStateException("RATE did not converge after " + MAX_ITERATIONS + " iterations");
+ }
+
+ /**
+ * Linear approximation for the initial Newton-Raphson guess: {@code r ≈ 2·(pmt·n + pv) / (pv·n)}. Falls back to
+ * {@value #DEFAULT_GUESS} if the estimate is non-positive.
+ */
+ private static BigDecimal estimateInitialGuess(final int nper, final BigDecimal pmt, final BigDecimal pv, final MathContext mc) {
+ final BigDecimal n = BigDecimal.valueOf(nper);
+ final BigDecimal pvTimesN = pv.multiply(n, mc);
+ if (pvTimesN.signum() == 0) {
+ return DEFAULT_GUESS;
+ }
+ final BigDecimal estimate = pmt.multiply(n, mc).add(pv, mc).multiply(TWO, mc).divide(pvTimesN, mc);
+ return estimate.signum() > 0 ? estimate : DEFAULT_GUESS;
+ }
+
+ /**
+ * Computes the discount factor: {@code 1 / (1 + eir)^days}.
+ *
+ * @param eir
+ * effective interest rate per period
+ * @param days
+ * number of periods to discount
+ * @param mc
+ * math context for precision
+ * @return the discount factor (1.0 when days=0)
+ * @throws IllegalArgumentException
+ * if days is negative or exceeds {@link Integer#MAX_VALUE}
+ */
+ public static BigDecimal discountFactor(final BigDecimal eir, final long days, final MathContext mc) {
+ if (days == 0) {
+ return BigDecimal.ONE;
+ }
+ if (days < 0 || days > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("days must be in [0, " + Integer.MAX_VALUE + "], got: " + days);
+ }
+ return BigDecimal.ONE.divide(BigDecimal.ONE.add(eir, mc).pow((int) days, mc), mc);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/ProjectedAmortizationScheduleData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/ProjectedAmortizationScheduleData.java
new file mode 100644
index 0000000000..d5b34284f6
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/ProjectedAmortizationScheduleData.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+@AllArgsConstructor
+public class ProjectedAmortizationScheduleData {
+
+ private final BigDecimal originationFeeAmount;
+ private final BigDecimal netDisbursementAmount;
+ private final BigDecimal totalPaymentValue;
+ private final BigDecimal periodPaymentRate;
+ private final int npvDayCount;
+ private final LocalDate expectedDisbursementDate;
+ private final BigDecimal expectedPaymentAmount;
+ private final int loanTerm;
+ private final BigDecimal effectiveInterestRate;
+ private final List<ProjectedAmortizationSchedulePaymentData> payments;
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/ProjectedAmortizationScheduleGenerateRequest.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/ProjectedAmortizationScheduleGenerateRequest.java
new file mode 100644
index 0000000000..380f0f228d
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/ProjectedAmortizationScheduleGenerateRequest.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public class ProjectedAmortizationScheduleGenerateRequest {
+
+ private BigDecimal originationFeeAmount;
+ private BigDecimal netDisbursementAmount;
+ private BigDecimal totalPaymentValue;
+ private BigDecimal periodPaymentRate;
+ private int npvDayCount;
+ private LocalDate expectedDisbursementDate;
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/ProjectedAmortizationSchedulePaymentData.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/ProjectedAmortizationSchedulePaymentData.java
new file mode 100644
index 0000000000..34bc396156
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/data/ProjectedAmortizationSchedulePaymentData.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+@AllArgsConstructor
+public class ProjectedAmortizationSchedulePaymentData {
+
+ private final int paymentNo;
+ private final LocalDate paymentDate;
+ private final long count;
+ private final long paymentsLeft;
+ private final BigDecimal expectedPaymentAmount;
+ private final BigDecimal forecastPaymentAmount;
+ private final BigDecimal discountFactor;
+ private final BigDecimal npvValue;
+ private final BigDecimal balance;
+ private final BigDecimal expectedAmortizationAmount;
+ private final BigDecimal netAmortizationAmount;
+ private final BigDecimal actualPaymentAmount;
+ private final BigDecimal actualAmortizationAmount;
+ private final BigDecimal incomeModification;
+ private final BigDecimal deferredBalance;
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/ProjectedAmortizationLoanModel.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/ProjectedAmortizationLoanModel.java
new file mode 100644
index 0000000000..4ea373f47f
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/domain/ProjectedAmortizationLoanModel.java
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.domain;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.OneToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.Version;
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
+
+@Entity
+@Table(name = "m_wc_loan_amortization_model")
+@Getter
+@Setter
+public class ProjectedAmortizationLoanModel extends AbstractPersistableCustom<Long> {
+
+ @Version
+ int version;
+
+ @OneToOne
+ @JoinColumn(name = "loan_id", nullable = false)
+ private WorkingCapitalLoan loan;
+
+ @Column(name = "json_model", columnDefinition = "text", nullable = false)
+ private String jsonModel;
+
+ @Column(name = "business_date", nullable = false)
+ private LocalDate businessDate;
+
+ @Column(name = "last_modified_on_utc", nullable = false)
+ private OffsetDateTime lastModifiedDate;
+
+ @Column(name = "json_model_version", nullable = false)
+ private String jsonModelVersion;
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/exception/ProjectedAmortizationScheduleNotFoundException.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/exception/ProjectedAmortizationScheduleNotFoundException.java
new file mode 100644
index 0000000000..7742ce3da2
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/exception/ProjectedAmortizationScheduleNotFoundException.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.exception;
+
+import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
+
+public class ProjectedAmortizationScheduleNotFoundException extends AbstractPlatformResourceNotFoundException {
+
+ public ProjectedAmortizationScheduleNotFoundException(final Long loanId) {
+ super("error.msg.wc.loan.amortization.schedule.not.found",
+ "Projected amortization schedule for Working Capital Loan " + loanId + " does not exist", loanId);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/ProjectedAmortizationScheduleMapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/ProjectedAmortizationScheduleMapper.java
new file mode 100644
index 0000000000..9888c92290
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/mapper/ProjectedAmortizationScheduleMapper.java
@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.mapper;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.workingcapitalloan.calc.ProjectedAmortizationScheduleModel;
+import org.apache.fineract.portfolio.workingcapitalloan.calc.ProjectedPayment;
+import org.apache.fineract.portfolio.workingcapitalloan.data.ProjectedAmortizationScheduleData;
+import org.apache.fineract.portfolio.workingcapitalloan.data.ProjectedAmortizationSchedulePaymentData;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ProjectedAmortizationScheduleMapper {
+
+ private static final int DISPLAY_SCALE = 2;
+ private static final RoundingMode DISPLAY_ROUNDING = RoundingMode.HALF_UP;
+
+ public ProjectedAmortizationScheduleData toData(final ProjectedAmortizationScheduleModel model) {
+ final List<ProjectedAmortizationSchedulePaymentData> paymentDataList = model.payments().stream().map(this::toPaymentData).toList();
+
+ return ProjectedAmortizationScheduleData.builder() //
+ .originationFeeAmount(roundMoney(model.originationFeeAmount())) //
+ .netDisbursementAmount(roundMoney(model.netDisbursementAmount())) //
+ .totalPaymentValue(roundMoney(model.totalPaymentValue())) //
+ .periodPaymentRate(model.periodPaymentRate()) //
+ .npvDayCount(model.npvDayCount()) //
+ .expectedDisbursementDate(model.expectedDisbursementDate()) //
+ .expectedPaymentAmount(roundMoney(model.expectedPaymentAmount())) //
+ .loanTerm(model.loanTerm()) //
+ .effectiveInterestRate(model.effectiveInterestRate()) //
+ .payments(paymentDataList) //
+ .build();
+ }
+
+ private ProjectedAmortizationSchedulePaymentData toPaymentData(final ProjectedPayment payment) {
+ return ProjectedAmortizationSchedulePaymentData.builder() //
+ .paymentNo(payment.paymentNo()) //
+ .paymentDate(payment.date()) //
+ .count(payment.count()) //
+ .paymentsLeft(payment.paymentsLeft()) //
+ .expectedPaymentAmount(roundMoney(payment.expectedPaymentAmount())) //
+ .forecastPaymentAmount(roundMoney(payment.forecastPaymentAmount())) //
+ .discountFactor(payment.discountFactor()) //
+ .npvValue(roundMoney(payment.npvValue())) //
+ .balance(roundMoney(payment.balance())) //
+ .expectedAmortizationAmount(roundMoney(payment.expectedAmortizationAmount())) //
+ .netAmortizationAmount(roundMoney(payment.netAmortizationAmount())) //
+ .actualPaymentAmount(roundMoney(payment.actualPaymentAmount())) //
+ .actualAmortizationAmount(roundMoney(payment.actualAmortizationAmount())) //
+ .incomeModification(roundMoney(payment.incomeModification())) //
+ .deferredBalance(roundMoney(payment.deferredBalance())) //
+ .build();
+ }
+
+ private static BigDecimal roundMoney(final Money value) {
+ return value != null ? value.getAmount().setScale(DISPLAY_SCALE, DISPLAY_ROUNDING) : null;
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/ProjectedAmortizationLoanModelRepository.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/ProjectedAmortizationLoanModelRepository.java
new file mode 100644
index 0000000000..5177341e25
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/repository/ProjectedAmortizationLoanModelRepository.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.repository;
+
+import java.util.Optional;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.ProjectedAmortizationLoanModel;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface ProjectedAmortizationLoanModelRepository
+ extends JpaSpecificationExecutor<ProjectedAmortizationLoanModel>, JpaRepository<ProjectedAmortizationLoanModel, Long> {
+
+ Optional<ProjectedAmortizationLoanModel> findByLoanId(Long loanId);
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleModelParserService.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleModelParserService.java
new file mode 100644
index 0000000000..62a1cfacd1
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleModelParserService.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.service;
+
+import java.math.MathContext;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.workingcapitalloan.calc.ProjectedAmortizationScheduleModel;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+
+public interface ProjectedAmortizationScheduleModelParserService {
+
+ String toJson(@NonNull ProjectedAmortizationScheduleModel model);
+
+ @Nullable
+ ProjectedAmortizationScheduleModel fromJson(@Nullable String json, @NonNull MathContext mc, @NonNull MonetaryCurrency currency);
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleModelParserServiceGsonImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleModelParserServiceGsonImpl.java
new file mode 100644
index 0000000000..3de2eced79
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleModelParserServiceGsonImpl.java
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.service;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.InstanceCreator;
+import com.google.gson.ToNumberPolicy;
+import java.math.MathContext;
+import java.time.LocalDate;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.serialization.gson.JsonExcludeAnnotationBasedExclusionStrategy;
+import org.apache.fineract.infrastructure.core.serialization.gson.LocalDateAdapter;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.serialization.MoneyDeserializer;
+import org.apache.fineract.organisation.monetary.serialization.MoneySerializer;
+import org.apache.fineract.portfolio.workingcapitalloan.calc.ProjectedAmortizationScheduleModel;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class ProjectedAmortizationScheduleModelParserServiceGsonImpl implements ProjectedAmortizationScheduleModelParserService {
+
+ private final Gson serializer = createSerializer();
+
+ private static Gson createSerializer() {
+ return new GsonBuilder() //
+ .registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe()) //
+ .registerTypeAdapter(Money.class, new MoneySerializer()) //
+ .setNumberToNumberStrategy(ToNumberPolicy.BIG_DECIMAL) //
+ .addSerializationExclusionStrategy(new JsonExcludeAnnotationBasedExclusionStrategy()) //
+ .addDeserializationExclusionStrategy(new JsonExcludeAnnotationBasedExclusionStrategy()) //
+ .create();
+ }
+
+ private static Gson createDeserializer(final MathContext mc, final MonetaryCurrency currency) {
+ return new GsonBuilder() //
+ .registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe()) //
+ .registerTypeAdapter(Money.class, new MoneyDeserializer(mc, currency)) //
+ .setNumberToNumberStrategy(ToNumberPolicy.BIG_DECIMAL) //
+ .registerTypeAdapter(ProjectedAmortizationScheduleModel.class,
+ (InstanceCreator<ProjectedAmortizationScheduleModel>) type -> ProjectedAmortizationScheduleModel
+ .forDeserialization(mc, currency)) //
+ .addSerializationExclusionStrategy(new JsonExcludeAnnotationBasedExclusionStrategy()) //
+ .addDeserializationExclusionStrategy(new JsonExcludeAnnotationBasedExclusionStrategy()) //
+ .create();
+ }
+
+ @Override
+ public String toJson(@NonNull final ProjectedAmortizationScheduleModel model) {
+ return serializer.toJson(model);
+ }
+
+ @Override
+ @Nullable
+ public ProjectedAmortizationScheduleModel fromJson(@Nullable final String json, @NonNull final MathContext mc,
+ @NonNull final MonetaryCurrency currency) {
+ if (json == null) {
+ return null;
+ }
+ try {
+ return createDeserializer(mc, currency).fromJson(json, ProjectedAmortizationScheduleModel.class);
+ } catch (Exception e) {
+ log.warn("Failed to parse ProjectedAmortizationScheduleModel JSON. Falling back to null.", e);
+ return null;
+ }
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleRepositoryWrapper.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleRepositoryWrapper.java
new file mode 100644
index 0000000000..98c6b95dd9
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleRepositoryWrapper.java
@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.service;
+
+import java.math.MathContext;
+import java.util.Optional;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.workingcapitalloan.calc.ProjectedAmortizationScheduleModel;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan;
+import org.springframework.lang.NonNull;
+
+public interface ProjectedAmortizationScheduleRepositoryWrapper {
+
+ Optional<ProjectedAmortizationScheduleModel> readModel(Long loanId, @NonNull MathContext mc, @NonNull MonetaryCurrency currency);
+
+ void writeModel(@NonNull WorkingCapitalLoan loan, @NonNull ProjectedAmortizationScheduleModel model);
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleRepositoryWrapperImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleRepositoryWrapperImpl.java
new file mode 100644
index 0000000000..b0448a73e7
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/ProjectedAmortizationScheduleRepositoryWrapperImpl.java
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.service;
+
+import java.math.MathContext;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.portfolio.workingcapitalloan.calc.ProjectedAmortizationScheduleModel;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.ProjectedAmortizationLoanModel;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan;
+import org.apache.fineract.portfolio.workingcapitalloan.repository.ProjectedAmortizationLoanModelRepository;
+import org.springframework.lang.NonNull;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+public class ProjectedAmortizationScheduleRepositoryWrapperImpl implements ProjectedAmortizationScheduleRepositoryWrapper {
+
+ private final ProjectedAmortizationLoanModelRepository repository;
+ private final ProjectedAmortizationScheduleModelParserService parserService;
+
+ @Override
+ public Optional<ProjectedAmortizationScheduleModel> readModel(final Long loanId, @NonNull final MathContext mc,
+ @NonNull final MonetaryCurrency currency) {
+ return repository.findByLoanId(loanId) //
+ .map(ProjectedAmortizationLoanModel::getJsonModel) //
+ .map(json -> parserService.fromJson(json, mc, currency));
+ }
+
+ @Override
+ @Transactional
+ public void writeModel(@NonNull final WorkingCapitalLoan loan, @NonNull final ProjectedAmortizationScheduleModel model) {
+ final String jsonModel = parserService.toJson(model);
+ final ProjectedAmortizationLoanModel entity = repository.findByLoanId(loan.getId()).orElseGet(() -> {
+ final ProjectedAmortizationLoanModel newEntity = new ProjectedAmortizationLoanModel();
+ newEntity.setLoan(loan);
+ return newEntity;
+ });
+ entity.setBusinessDate(ThreadLocalContextUtil.getBusinessDate());
+ entity.setLastModifiedDate(DateUtils.getAuditOffsetDateTime());
+ entity.setJsonModel(jsonModel);
+ entity.setJsonModelVersion(ProjectedAmortizationScheduleModel.getModelVersion());
+ repository.save(entity);
+ }
+
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleReadService.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleReadService.java
new file mode 100644
index 0000000000..137a1bd30f
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleReadService.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.service;
+
+import org.apache.fineract.portfolio.workingcapitalloan.data.ProjectedAmortizationScheduleData;
+
+public interface WorkingCapitalLoanAmortizationScheduleReadService {
+
+ ProjectedAmortizationScheduleData retrieveAmortizationSchedule(Long loanId);
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleReadServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleReadServiceImpl.java
new file mode 100644
index 0000000000..b16d947236
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleReadServiceImpl.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.service;
+
+import java.math.MathContext;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.workingcapitalloan.calc.ProjectedAmortizationScheduleModel;
+import org.apache.fineract.portfolio.workingcapitalloan.data.ProjectedAmortizationScheduleData;
+import org.apache.fineract.portfolio.workingcapitalloan.exception.ProjectedAmortizationScheduleNotFoundException;
+import org.apache.fineract.portfolio.workingcapitalloan.exception.WorkingCapitalLoanNotFoundException;
+import org.apache.fineract.portfolio.workingcapitalloan.mapper.ProjectedAmortizationScheduleMapper;
+import org.apache.fineract.portfolio.workingcapitalloan.repository.WorkingCapitalLoanRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@Transactional(readOnly = true)
+public class WorkingCapitalLoanAmortizationScheduleReadServiceImpl implements WorkingCapitalLoanAmortizationScheduleReadService {
+
+ // TODO: currency should come from loan product once WCL lifecycle is implemented
+ private static final MonetaryCurrency DEFAULT_CURRENCY = new MonetaryCurrency("USD", 2, null);
+
+ private final WorkingCapitalLoanRepository loanRepository;
+ private final ProjectedAmortizationScheduleRepositoryWrapper scheduleRepositoryWrapper;
+ private final ProjectedAmortizationScheduleMapper mapper;
+
+ @Override
+ public ProjectedAmortizationScheduleData retrieveAmortizationSchedule(final Long loanId) {
+ if (!loanRepository.existsById(loanId)) {
+ throw new WorkingCapitalLoanNotFoundException(loanId);
+ }
+
+ final MathContext mc = MoneyHelper.getMathContext();
+ final ProjectedAmortizationScheduleModel model = scheduleRepositoryWrapper.readModel(loanId, mc, DEFAULT_CURRENCY)
+ .orElseThrow(() -> new ProjectedAmortizationScheduleNotFoundException(loanId));
+
+ return mapper.toData(model);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleWriteService.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleWriteService.java
new file mode 100644
index 0000000000..2f6958cbad
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleWriteService.java
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.service;
+
+import org.apache.fineract.portfolio.workingcapitalloan.data.ProjectedAmortizationScheduleGenerateRequest;
+
+public interface WorkingCapitalLoanAmortizationScheduleWriteService {
+
+ void generateAndSaveAmortizationSchedule(Long loanId, ProjectedAmortizationScheduleGenerateRequest request);
+}
diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleWriteServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleWriteServiceImpl.java
new file mode 100644
index 0000000000..f8729c0b61
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanAmortizationScheduleWriteServiceImpl.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.service;
+
+import java.math.MathContext;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.workingcapitalloan.calc.ProjectedAmortizationScheduleModel;
+import org.apache.fineract.portfolio.workingcapitalloan.data.ProjectedAmortizationScheduleGenerateRequest;
+import org.apache.fineract.portfolio.workingcapitalloan.domain.WorkingCapitalLoan;
+import org.apache.fineract.portfolio.workingcapitalloan.exception.WorkingCapitalLoanNotFoundException;
+import org.apache.fineract.portfolio.workingcapitalloan.repository.WorkingCapitalLoanRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+// TODO: This is a temporary testing implementation. In the real flow, the amortization schedule
+// will be generated and saved as part of the loan lifecycle (approve/disburse) — not via a
+// standalone endpoint. The parameters will come from the loan entity + product, not from the
+// request body. Replace this once the full WCL lifecycle is implemented.
+@Service
+@RequiredArgsConstructor
+@Transactional
+public class WorkingCapitalLoanAmortizationScheduleWriteServiceImpl implements WorkingCapitalLoanAmortizationScheduleWriteService {
+
+ // TODO: currency should come from loan product once WCL lifecycle is implemented
+ private static final MonetaryCurrency DEFAULT_CURRENCY = new MonetaryCurrency("USD", 2, null);
+
+ private final WorkingCapitalLoanRepository loanRepository;
+ private final ProjectedAmortizationScheduleRepositoryWrapper scheduleRepositoryWrapper;
+
+ @Override
+ public void generateAndSaveAmortizationSchedule(final Long loanId, final ProjectedAmortizationScheduleGenerateRequest request) {
+ final WorkingCapitalLoan loan = loanRepository.findById(loanId).orElseThrow(() -> new WorkingCapitalLoanNotFoundException(loanId));
+
+ final MathContext mc = MoneyHelper.getMathContext();
+
+ final ProjectedAmortizationScheduleModel model = ProjectedAmortizationScheduleModel.generate(//
+ request.getOriginationFeeAmount(), //
+ request.getNetDisbursementAmount(), //
+ request.getTotalPaymentValue(), //
+ request.getPeriodPaymentRate(), //
+ request.getNpvDayCount(), //
+ request.getExpectedDisbursementDate(), //
+ mc, DEFAULT_CURRENCY);
+
+ scheduleRepositoryWrapper.writeModel(loan, model);
+ }
+}
diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml
index 47ef0f45d5..d49e648c1d 100644
--- a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml
+++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/module-changelog-master.xml
@@ -30,4 +30,5 @@
<include relativeToChangelogFile="true" file="parts/0006_wc_loan_disbursement_details.xml"/>
<include relativeToChangelogFile="true" file="parts/0007_drop_flat_percentage_amount.xml"/>
<include relativeToChangelogFile="true" file="parts/0008_delinquency_for_working_capital_loans.xml"/>
+ <include relativeToChangelogFile="true" file="parts/0009_wc_loan_amortization_model.xml"/>
</databaseChangeLog>
diff --git a/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0009_wc_loan_amortization_model.xml b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0009_wc_loan_amortization_model.xml
new file mode 100644
index 0000000000..90f085f069
--- /dev/null
+++ b/fineract-working-capital-loan/src/main/resources/db/changelog/tenant/module/workingcapitalloan/parts/0009_wc_loan_amortization_model.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
+
+ <changeSet id="wcl-0008-1" author="fineract">
+ <preConditions onFail="MARK_RAN">
+ <not>
+ <tableExists tableName="m_wc_loan_amortization_model"/>
+ </not>
+ </preConditions>
+ <createTable tableName="m_wc_loan_amortization_model">
+ <column autoIncrement="true" name="id" type="BIGINT">
+ <constraints nullable="false" primaryKey="true"/>
+ </column>
+ <column name="version" type="INT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="loan_id" type="BIGINT">
+ <constraints nullable="false"/>
+ </column>
+ <column name="json_model" type="CLOB">
+ <constraints nullable="false"/>
+ </column>
+ <column name="business_date" type="DATE">
+ <constraints nullable="false"/>
+ </column>
+ <column name="json_model_version" type="VARCHAR(10)">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+ </changeSet>
+
+ <changeSet id="wcl-0008-2" author="fineract">
+ <addUniqueConstraint columnNames="loan_id" constraintName="uc_m_wc_loan_amort_model_loan"
+ tableName="m_wc_loan_amortization_model"/>
+ </changeSet>
+
+ <changeSet id="wcl-0008-3" author="fineract">
+ <addForeignKeyConstraint baseColumnNames="loan_id" baseTableName="m_wc_loan_amortization_model"
+ constraintName="fk_m_wc_loan_amort_model_loan" referencedColumnNames="id"
+ referencedTableName="m_wc_loan"/>
+ </changeSet>
+
+ <changeSet author="fineract" id="wcl-0008-4-my" context="mysql">
+ <addColumn tableName="m_wc_loan_amortization_model">
+ <column name="last_modified_on_utc" type="DATETIME(6)">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ </changeSet>
+
+ <changeSet author="fineract" id="wcl-0008-4-pg" context="postgresql">
+ <addColumn tableName="m_wc_loan_amortization_model">
+ <column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ </changeSet>
+
+</databaseChangeLog>
diff --git a/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedAmortizationScheduleCalculatorTest.java b/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedAmortizationScheduleCalculatorTest.java
new file mode 100644
index 0000000000..e09a2cf63c
--- /dev/null
+++ b/fineract-working-capital-loan/src/test/java/org/apache/fineract/portfolio/workingcapitalloan/calc/ProjectedAmortizationScheduleCalculatorTest.java
@@ -0,0 +1,2316 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.workingcapitalloan.calc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.junit.jupiter.api.Test;
+
+class ProjectedAmortizationScheduleCalculatorTest {
+
+ private static final MathContext MC = MathContext.DECIMAL128;
+ private static final MonetaryCurrency CURRENCY = new MonetaryCurrency("USD", 2, null);
+
+ private static final BigDecimal ORIGINATION_FEE = new BigDecimal("1000");
+ private static final BigDecimal NET_DISBURSEMENT = new BigDecimal("9000");
+ private static final BigDecimal TPV = new BigDecimal("100000");
+ private static final BigDecimal RATE = new BigDecimal("0.18");
+ private static final int DAY_COUNT = 360;
+ private static final LocalDate EXPECTED_DISBURSEMENT_DATE = LocalDate.of(2019, 1, 1);
+ private static final int TERM = 200;
+
+ private final ProjectedAmortizationScheduleCalculator calculator = new DefaultProjectedAmortizationScheduleCalculator();
+
+ @Test
+ void testAddDisbursement_term10_originationFee50_netDisbursement450_then430() {
+ final BigDecimal originationFee = new BigDecimal("50");
+ final BigDecimal initialNetDisbursement = new BigDecimal("450");
+ final LocalDate initialDisbursementDate = LocalDate.of(2019, 1, 1);
+
+ final ProjectedAmortizationScheduleModel initial = calculator.generateModel(originationFee, initialNetDisbursement, TPV, RATE,
+ DAY_COUNT, initialDisbursementDate, MC, CURRENCY);
+ final ProjectedAmortizationScheduleModel model1 = calculator.addDisbursement(initial, originationFee, initialNetDisbursement,
+ initialDisbursementDate);
+
+ assertEquals(10, model1.loanTerm());
+ assertEquals(11, model1.payments().size());
+
+ checkInst(model1, 0, 0, LocalDate.of(2019, 1, 1), 10, 0, -450.00, null, null, 1.00000000, -450.00, 450.00, null, null, null, null,
+ 50.00);
+ checkInst(model1, 1, 1, LocalDate.of(2019, 1, 2), 9, 1, 50.00, 50.00, null, 0.98074794, 49.04, 408.83, 8.83, 0.00, null, -8.83,
+ 50.00);
+ checkInst(model1, 2, 2, LocalDate.of(2019, 1, 3), 8, 2, 50.00, 50.00, null, 0.96186652, 48.09, 366.86, 8.03, 0.00, null, -8.03,
+ 50.00);
+ checkInst(model1, 3, 3, LocalDate.of(2019, 1, 4), 7, 3, 50.00, 50.00, null, 0.94334860, 47.17, 324.06, 7.20, 0.00, null, -7.20,
+ 50.00);
+ checkInst(model1, 4, 4, LocalDate.of(2019, 1, 5), 6, 4, 50.00, 50.00, null, 0.92518720, 46.26, 280.42, 6.36, 0.00, null, -6.36,
+ 50.00);
+ checkInst(model1, 5, 5, LocalDate.of(2019, 1, 6), 5, 5, 50.00, 50.00, null, 0.90737544, 45.37, 235.93, 5.50, 0.00, null, -5.50,
+ 50.00);
+ checkInst(model1, 6, 6, LocalDate.of(2019, 1, 7), 4, 6, 50.00, 50.00, null, 0.88990659, 44.50, 190.56, 4.63, 0.00, null, -4.63,
+ 50.00);
+ checkInst(model1, 7, 7, LocalDate.of(2019, 1, 8), 3, 7, 50.00, 50.00, null, 0.87277405, 43.64, 144.30, 3.74, 0.00, null, -3.74,
+ 50.00);
+ checkInst(model1, 8, 8, LocalDate.of(2019, 1, 9), 2, 8, 50.00, 50.00, null, 0.85597135, 42.80, 97.13, 2.83, 0.00, null, -2.83,
+ 50.00);
+ checkInst(model1, 9, 9, LocalDate.of(2019, 1, 10), 1, 9, 50.00, 50.00, null, 0.83949214, 41.97, 49.04, 1.91, 0.00, null, -1.91,
+ 50.00);
+ checkInst(model1, 10, 10, LocalDate.of(2019, 1, 11), 0, 10, 50.00, 50.00, null, 0.82333018, 41.17, 0.00, 0.96, 0.00, null, -0.96,
+ 50.00);
+
+ final BigDecimal newNetDisbursement = new BigDecimal("430");
+ final LocalDate newDisbursementDate = LocalDate.of(2019, 1, 5);
+
+ final ProjectedAmortizationScheduleModel model2 = calculator.addDisbursement(model1, originationFee, newNetDisbursement,
+ newDisbursementDate);
+
+ assertEquals(10, model2.loanTerm());
+ assertEquals(11, model2.payments().size());
+
+ checkInst(model2, 0, 0, LocalDate.of(2019, 1, 5), 10, 0, -430.00, null, null, 1.00000000, -430.00, 430.00, null, null, null, null,
+ 50.00);
+ checkInst(model2, 1, 1, LocalDate.of(2019, 1, 6), 9, 1, 50.00, 50.00, null, 0.97237826, 48.62, 392.21, 12.21, 0.00, null, -12.21,
+ 50.00);
+ checkInst(model2, 2, 2, LocalDate.of(2019, 1, 7), 8, 2, 50.00, 50.00, null, 0.94551948, 47.28, 353.36, 11.14, 0.00, null, -11.14,
+ 50.00);
+ checkInst(model2, 3, 3, LocalDate.of(2019, 1, 8), 7, 3, 50.00, 50.00, null, 0.91940259, 45.97, 313.39, 10.04, 0.00, null, -10.04,
+ 50.00);
+ checkInst(model2, 4, 4, LocalDate.of(2019, 1, 9), 6, 4, 50.00, 50.00, null, 0.89400709, 44.70, 272.30, 8.90, 0.00, null, -8.90,
+ 50.00);
+ checkInst(model2, 5, 5, LocalDate.of(2019, 1, 10), 5, 5, 50.00, 50.00, null, 0.86931306, 43.47, 230.03, 7.73, 0.00, null, -7.73,
+ 50.00);
+ checkInst(model2, 6, 6, LocalDate.of(2019, 1, 11), 4, 6, 50.00, 50.00, null, 0.84530113, 42.27, 186.57, 6.53, 0.00, null, -6.53,
+ 50.00);
+ checkInst(model2, 7, 7, LocalDate.of(2019, 1, 12), 3, 7, 50.00, 50.00, null, 0.82195244, 41.10, 141.87, 5.30, 0.00, null, -5.30,
+ 50.00);
+ checkInst(model2, 8, 8, LocalDate.of(2019, 1, 13), 2, 8, 50.00, 50.00, null, 0.79924869, 39.96, 95.89, 4.03, 0.00, null, -4.03,
+ 50.00);
+ checkInst(model2, 9, 9, LocalDate.of(2019, 1, 14), 1, 9, 50.00, 50.00, null, 0.77717205, 38.86, 48.62, 2.72, 0.00, null, -2.72,
+ 50.00);
+ checkInst(model2, 10, 10, LocalDate.of(2019, 1, 15), 0, 10, 50.00, 50.00, null, 0.75570520, 37.79, 0.00, 1.38, 0.00, null, -1.38,
+ 50.00);
+ }
+
+ @Test
+ void testProjectedSchedule_term200_originationFee1000_netDisbursement9000() {
+ final ProjectedAmortizationScheduleModel model = generateModel();
+
+ assertEquals(TERM, model.loanTerm());
+
+ checkInst(model, 0, 0, EXPECTED_DISBURSEMENT_DATE, 200, 0, -9000.00, null, null, 1.00000000, -9000.00, 9000.00, null, null, null,
+ null, 1000.00);
+
+ checkInst(model, 1, 1, LocalDate.of(2019, 1, 2), 199, 1, 50.00, 50.00, null, 0.99893332, 49.95, 8959.61, 9.61, 0.00, null, -9.61,
+ 1000.00);
+ checkInst(model, 2, 2, LocalDate.of(2019, 1, 3), 198, 2, 50.00, 50.00, null, 0.99786779, 49.89, 8919.18, 9.57, 0.00, null, -9.57,
+ 1000.00);
+ checkInst(model, 3, 3, LocalDate.of(2019, 1, 4), 197, 3, 50.00, 50.00, null, 0.99680339, 49.84, 8878.70, 9.52, 0.00, null, -9.52,
+ 1000.00);
+ checkInst(model, 4, 4, LocalDate.of(2019, 1, 5), 196, 4, 50.00, 50.00, null, 0.99574012, 49.79, 8838.18, 9.48, 0.00, null, -9.48,
+ 1000.00);
+ checkInst(model, 5, 5, LocalDate.of(2019, 1, 6), 195, 5, 50.00, 50.00, null, 0.99467799, 49.73, 8797.62, 9.44, 0.00, null, -9.44,
+ 1000.00);
+ checkInst(model, 6, 6, LocalDate.of(2019, 1, 7), 194, 6, 50.00, 50.00, null, 0.99361699, 49.68, 8757.01, 9.39, 0.00, null, -9.39,
+ 1000.00);
+ checkInst(model, 7, 7, LocalDate.of(2019, 1, 8), 193, 7, 50.00, 50.00, null, 0.99255712, 49.63, 8716.36, 9.35, 0.00, null, -9.35,
+ 1000.00);
+ checkInst(model, 8, 8, LocalDate.of(2019, 1, 9), 192, 8, 50.00, 50.00, null, 0.99149839, 49.57, 8675.67, 9.31, 0.00, null, -9.31,
+ 1000.00);
+ checkInst(model, 9, 9, LocalDate.of(2019, 1, 10), 191, 9, 50.00, 50.00, null, 0.99044078, 49.52, 8634.94, 9.26, 0.00, null, -9.26,
+ 1000.00);
+ checkInst(model, 10, 10, LocalDate.of(2019, 1, 11), 190, 10, 50.00, 50.00, null, 0.98938430, 49.47, 8594.16, 9.22, 0.00, null,
+ -9.22, 1000.00);
+ checkInst(model, 11, 11, LocalDate.of(2019, 1, 12), 189, 11, 50.00, 50.00, null, 0.98832895, 49.42, 8553.33, 9.18, 0.00, null,
+ -9.18, 1000.00);
+ checkInst(model, 12, 12, LocalDate.of(2019, 1, 13), 188, 12, 50.00, 50.00, null, 0.98727472, 49.36, 8512.47, 9.13, 0.00, null,
+ -9.13, 1000.00);
+ checkInst(model, 13, 13, LocalDate.of(2019, 1, 14), 187, 13, 50.00, 50.00, null, 0.98622162, 49.31, 8471.56, 9.09, 0.00, null,
+ -9.09, 1000.00);
+ checkInst(model, 14, 14, LocalDate.of(2019, 1, 15), 186, 14, 50.00, 50.00, null, 0.98516964, 49.26, 8430.60, 9.05, 0.00, null,
+ -9.05, 1000.00);
+ checkInst(model, 15, 15, LocalDate.of(2019, 1, 16), 185, 15, 50.00, 50.00, null, 0.98411879, 49.21, 8389.61, 9.00, 0.00, null,
+ -9.00, 1000.00);
+ checkInst(model, 16, 16, LocalDate.of(2019, 1, 17), 184, 16, 50.00, 50.00, null, 0.98306905, 49.15, 8348.56, 8.96, 0.00, null,
+ -8.96, 1000.00);
+ checkInst(model, 17, 17, LocalDate.of(2019, 1, 18), 183, 17, 50.00, 50.00, null, 0.98202044, 49.10, 8307.48, 8.91, 0.00, null,
+ -8.91, 1000.00);
+ checkInst(model, 18, 18, LocalDate.of(2019, 1, 19), 182, 18, 50.00, 50.00, null, 0.98097294, 49.05, 8266.35, 8.87, 0.00, null,
+ -8.87, 1000.00);
+ checkInst(model, 19, 19, LocalDate.of(2019, 1, 20), 181, 19, 50.00, 50.00, null, 0.97992656, 49.00, 8225.18, 8.83, 0.00, null,
+ -8.83, 1000.00);
+ checkInst(model, 20, 20, LocalDate.of(2019, 1, 21), 180, 20, 50.00, 50.00, null, 0.97888129, 48.94, 8183.96, 8.78, 0.00, null,
+ -8.78, 1000.00);
+ checkInst(model, 21, 21, LocalDate.of(2019, 1, 22), 179, 21, 50.00, 50.00, null, 0.97783715, 48.89, 8142.70, 8.74, 0.00, null,
+ -8.74, 1000.00);
+ checkInst(model, 22, 22, LocalDate.of(2019, 1, 23), 178, 22, 50.00, 50.00, null, 0.97679411, 48.84, 8101.39, 8.69, 0.00, null,
+ -8.69, 1000.00);
+ checkInst(model, 23, 23, LocalDate.of(2019, 1, 24), 177, 23, 50.00, 50.00, null, 0.97575219, 48.79, 8060.04, 8.65, 0.00, null,
+ -8.65, 1000.00);
+ checkInst(model, 24, 24, LocalDate.of(2019, 1, 25), 176, 24, 50.00, 50.00, null, 0.97471138, 48.74, 8018.65, 8.61, 0.00, null,
+ -8.61, 1000.00);
+ checkInst(model, 25, 25, LocalDate.of(2019, 1, 26), 175, 25, 50.00, 50.00, null, 0.97367168, 48.68, 7977.21, 8.56, 0.00, null,
+ -8.56, 1000.00);
+ checkInst(model, 26, 26, LocalDate.of(2019, 1, 27), 174, 26, 50.00, 50.00, null, 0.97263309, 48.63, 7935.73, 8.52, 0.00, null,
+ -8.52, 1000.00);
+ checkInst(model, 27, 27, LocalDate.of(2019, 1, 28), 173, 27, 50.00, 50.00, null, 0.97159560, 48.58, 7894.21, 8.47, 0.00, null,
+ -8.47, 1000.00);
+ checkInst(model, 28, 28, LocalDate.of(2019, 1, 29), 172, 28, 50.00, 50.00, null, 0.97055922, 48.53, 7852.63, 8.43, 0.00, null,
+ -8.43, 1000.00);
+ checkInst(model, 29, 29, LocalDate.of(2019, 1, 30), 171, 29, 50.00, 50.00, null, 0.96952395, 48.48, 7811.02, 8.39, 0.00, null,
+ -8.39, 1000.00);
+ checkInst(model, 30, 30, LocalDate.of(2019, 1, 31), 170, 30, 50.00, 50.00, null, 0.96848979, 48.42, 7769.36, 8.34, 0.00, null,
+ -8.34, 1000.00);
+ checkInst(model, 31, 31, LocalDate.of(2019, 2, 1), 169, 31, 50.00, 50.00, null, 0.96745672, 48.37, 7727.66, 8.30, 0.00, null, -8.30,
+ 1000.00);
+ checkInst(model, 32, 32, LocalDate.of(2019, 2, 2), 168, 32, 50.00, 50.00, null, 0.96642476, 48.32, 7685.91, 8.25, 0.00, null, -8.25,
+ 1000.00);
+ checkInst(model, 33, 33, LocalDate.of(2019, 2, 3), 167, 33, 50.00, 50.00, null, 0.96539390, 48.27, 7644.12, 8.21, 0.00, null, -8.21,
+ 1000.00);
+ checkInst(model, 34, 34, LocalDate.of(2019, 2, 4), 166, 34, 50.00, 50.00, null, 0.96436413, 48.22, 7602.28, 8.16, 0.00, null, -8.16,
+ 1000.00);
+ checkInst(model, 35, 35, LocalDate.of(2019, 2, 5), 165, 35, 50.00, 50.00, null, 0.96333547, 48.17, 7560.40, 8.12, 0.00, null, -8.12,
+ 1000.00);
+ checkInst(model, 36, 36, LocalDate.of(2019, 2, 6), 164, 36, 50.00, 50.00, null, 0.96230790, 48.12, 7518.47, 8.07, 0.00, null, -8.07,
+ 1000.00);
+ checkInst(model, 37, 37, LocalDate.of(2019, 2, 7), 163, 37, 50.00, 50.00, null, 0.96128143, 48.06, 7476.50, 8.03, 0.00, null, -8.03,
+ 1000.00);
+ checkInst(model, 38, 38, LocalDate.of(2019, 2, 8), 162, 38, 50.00, 50.00, null, 0.96025606, 48.01, 7434.48, 7.98, 0.00, null, -7.98,
+ 1000.00);
+ checkInst(model, 39, 39, LocalDate.of(2019, 2, 9), 161, 39, 50.00, 50.00, null, 0.95923178, 47.96, 7392.42, 7.94, 0.00, null, -7.94,
+ 1000.00);
+ checkInst(model, 40, 40, LocalDate.of(2019, 2, 10), 160, 40, 50.00, 50.00, null, 0.95820859, 47.91, 7350.31, 7.89, 0.00, null,
+ -7.89, 1000.00);
+ checkInst(model, 41, 41, LocalDate.of(2019, 2, 11), 159, 41, 50.00, 50.00, null, 0.95718649, 47.86, 7308.16, 7.85, 0.00, null,
+ -7.85, 1000.00);
+ checkInst(model, 42, 42, LocalDate.of(2019, 2, 12), 158, 42, 50.00, 50.00, null, 0.95616548, 47.81, 7265.97, 7.80, 0.00, null,
+ -7.80, 1000.00);
+ checkInst(model, 43, 43, LocalDate.of(2019, 2, 13), 157, 43, 50.00, 50.00, null, 0.95514557, 47.76, 7223.72, 7.76, 0.00, null,
+ -7.76, 1000.00);
+ checkInst(model, 44, 44, LocalDate.of(2019, 2, 14), 156, 44, 50.00, 50.00, null, 0.95412674, 47.71, 7181.44, 7.71, 0.00, null,
+ -7.71, 1000.00);
+ checkInst(model, 45, 45, LocalDate.of(2019, 2, 15), 155, 45, 50.00, 50.00, null, 0.95310899, 47.66, 7139.11, 7.67, 0.00, null,
+ -7.67, 1000.00);
+ checkInst(model, 46, 46, LocalDate.of(2019, 2, 16), 154, 46, 50.00, 50.00, null, 0.95209233, 47.60, 7096.73, 7.62, 0.00, null,
+ -7.62, 1000.00);
+ checkInst(model, 47, 47, LocalDate.of(2019, 2, 17), 153, 47, 50.00, 50.00, null, 0.95107676, 47.55, 7054.31, 7.58, 0.00, null,
+ -7.58, 1000.00);
+ checkInst(model, 48, 48, LocalDate.of(2019, 2, 18), 152, 48, 50.00, 50.00, null, 0.95006227, 47.50, 7011.84, 7.53, 0.00, null,
+ -7.53, 1000.00);
+ checkInst(model, 49, 49, LocalDate.of(2019, 2, 19), 151, 49, 50.00, 50.00, null, 0.94904886, 47.45, 6969.33, 7.49, 0.00, null,
+ -7.49, 1000.00);
+ checkInst(model, 50, 50, LocalDate.of(2019, 2, 20), 150, 50, 50.00, 50.00, null, 0.94803653, 47.40, 6926.77, 7.44, 0.00, null,
+ -7.44, 1000.00);
+ checkInst(model, 51, 51, LocalDate.of(2019, 2, 21), 149, 51, 50.00, 50.00, null, 0.94702529, 47.35, 6884.17, 7.40, 0.00, null,
+ -7.40, 1000.00);
+ checkInst(model, 52, 52, LocalDate.of(2019, 2, 22), 148, 52, 50.00, 50.00, null, 0.94601512, 47.30, 6841.52, 7.35, 0.00, null,
+ -7.35, 1000.00);
+ checkInst(model, 53, 53, LocalDate.of(2019, 2, 23), 147, 53, 50.00, 50.00, null, 0.94500603, 47.25, 6798.82, 7.31, 0.00, null,
+ -7.31, 1000.00);
+ checkInst(model, 54, 54, LocalDate.of(2019, 2, 24), 146, 54, 50.00, 50.00, null, 0.94399801, 47.20, 6756.08, 7.26, 0.00, null,
+ -7.26, 1000.00);
+ checkInst(model, 55, 55, LocalDate.of(2019, 2, 25), 145, 55, 50.00, 50.00, null, 0.94299107, 47.15, 6713.30, 7.21, 0.00, null,
+ -7.21, 1000.00);
+ checkInst(model, 56, 56, LocalDate.of(2019, 2, 26), 144, 56, 50.00, 50.00, null, 0.94198521, 47.10, 6670.47, 7.17, 0.00, null,
+ -7.17, 1000.00);
+ checkInst(model, 57, 57, LocalDate.of(2019, 2, 27), 143, 57, 50.00, 50.00, null, 0.94098042, 47.05, 6627.59, 7.12, 0.00, null,
+ -7.12, 1000.00);
+ checkInst(model, 58, 58, LocalDate.of(2019, 2, 28), 142, 58, 50.00, 50.00, null, 0.93997669, 47.00, 6584.67, 7.08, 0.00, null,
+ -7.08, 1000.00);
+ checkInst(model, 59, 59, LocalDate.of(2019, 3, 1), 141, 59, 50.00, 50.00, null, 0.93897404, 46.95, 6541.70, 7.03, 0.00, null, -7.03,
+ 1000.00);
+ checkInst(model, 60, 60, LocalDate.of(2019, 3, 2), 140, 60, 50.00, 50.00, null, 0.93797246, 46.90, 6498.68, 6.99, 0.00, null, -6.99,
+ 1000.00);
+ checkInst(model, 61, 61, LocalDate.of(2019, 3, 3), 139, 61, 50.00, 50.00, null, 0.93697195, 46.85, 6455.62, 6.94, 0.00, null, -6.94,
+ 1000.00);
+ checkInst(model, 62, 62, LocalDate.of(2019, 3, 4), 138, 62, 50.00, 50.00, null, 0.93597251, 46.80, 6412.51, 6.89, 0.00, null, -6.89,
+ 1000.00);
+ checkInst(model, 63, 63, LocalDate.of(2019, 3, 5), 137, 63, 50.00, 50.00, null, 0.93497413, 46.75, 6369.36, 6.85, 0.00, null, -6.85,
+ 1000.00);
+ checkInst(model, 64, 64, LocalDate.of(2019, 3, 6), 136, 64, 50.00, 50.00, null, 0.93397681, 46.70, 6326.16, 6.80, 0.00, null, -6.80,
+ 1000.00);
+ checkInst(model, 65, 65, LocalDate.of(2019, 3, 7), 135, 65, 50.00, 50.00, null, 0.93298056, 46.65, 6282.92, 6.76, 0.00, null, -6.76,
+ 1000.00);
+ checkInst(model, 66, 66, LocalDate.of(2019, 3, 8), 134, 66, 50.00, 50.00, null, 0.93198538, 46.60, 6239.63, 6.71, 0.00, null, -6.71,
+ 1000.00);
+ checkInst(model, 67, 67, LocalDate.of(2019, 3, 9), 133, 67, 50.00, 50.00, null, 0.93099125, 46.55, 6196.29, 6.66, 0.00, null, -6.66,
+ 1000.00);
+ checkInst(model, 68, 68, LocalDate.of(2019, 3, 10), 132, 68, 50.00, 50.00, null, 0.92999818, 46.50, 6152.91, 6.62, 0.00, null,
+ -6.62, 1000.00);
+ checkInst(model, 69, 69, LocalDate.of(2019, 3, 11), 131, 69, 50.00, 50.00, null, 0.92900618, 46.45, 6109.48, 6.57, 0.00, null,
+ -6.57, 1000.00);
+ checkInst(model, 70, 70, LocalDate.of(2019, 3, 12), 130, 70, 50.00, 50.00, null, 0.92801523, 46.40, 6066.00, 6.52, 0.00, null,
+ -6.52, 1000.00);
+ checkInst(model, 71, 71, LocalDate.of(2019, 3, 13), 129, 71, 50.00, 50.00, null, 0.92702534, 46.35, 6022.48, 6.48, 0.00, null,
+ -6.48, 1000.00);
+ checkInst(model, 72, 72, LocalDate.of(2019, 3, 14), 128, 72, 50.00, 50.00, null, 0.92603650, 46.30, 5978.91, 6.43, 0.00, null,
+ -6.43, 1000.00);
+ checkInst(model, 73, 73, LocalDate.of(2019, 3, 15), 127, 73, 50.00, 50.00, null, 0.92504872, 46.25, 5935.29, 6.38, 0.00, null,
+ -6.38, 1000.00);
+ checkInst(model, 74, 74, LocalDate.of(2019, 3, 16), 126, 74, 50.00, 50.00, null, 0.92406200, 46.20, 5891.63, 6.34, 0.00, null,
+ -6.34, 1000.00);
+ checkInst(model, 75, 75, LocalDate.of(2019, 3, 17), 125, 75, 50.00, 50.00, null, 0.92307632, 46.15, 5847.92, 6.29, 0.00, null,
+ -6.29, 1000.00);
+ checkInst(model, 76, 76, LocalDate.of(2019, 3, 18), 124, 76, 50.00, 50.00, null, 0.92209170, 46.10, 5804.17, 6.24, 0.00, null,
+ -6.24, 1000.00);
+ checkInst(model, 77, 77, LocalDate.of(2019, 3, 19), 123, 77, 50.00, 50.00, null, 0.92110813, 46.06, 5760.36, 6.20, 0.00, null,
+ -6.20, 1000.00);
+ checkInst(model, 78, 78, LocalDate.of(2019, 3, 20), 122, 78, 50.00, 50.00, null, 0.92012560, 46.01, 5716.52, 6.15, 0.00, null,
+ -6.15, 1000.00);
+ checkInst(model, 79, 79, LocalDate.of(2019, 3, 21), 121, 79, 50.00, 50.00, null, 0.91914413, 45.96, 5672.62, 6.10, 0.00, null,
+ -6.10, 1000.00);
+ checkInst(model, 80, 80, LocalDate.of(2019, 3, 22), 120, 80, 50.00, 50.00, null, 0.91816370, 45.91, 5628.68, 6.06, 0.00, null,
+ -6.06, 1000.00);
+ checkInst(model, 81, 81, LocalDate.of(2019, 3, 23), 119, 81, 50.00, 50.00, null, 0.91718432, 45.86, 5584.69, 6.01, 0.00, null,
+ -6.01, 1000.00);
+ checkInst(model, 82, 82, LocalDate.of(2019, 3, 24), 118, 82, 50.00, 50.00, null, 0.91620598, 45.81, 5540.65, 5.96, 0.00, null,
+ -5.96, 1000.00);
+ checkInst(model, 83, 83, LocalDate.of(2019, 3, 25), 117, 83, 50.00, 50.00, null, 0.91522868, 45.76, 5496.57, 5.92, 0.00, null,
+ -5.92, 1000.00);
+ checkInst(model, 84, 84, LocalDate.of(2019, 3, 26), 116, 84, 50.00, 50.00, null, 0.91425243, 45.71, 5452.44, 5.87, 0.00, null,
+ -5.87, 1000.00);
+ checkInst(model, 85, 85, LocalDate.of(2019, 3, 27), 115, 85, 50.00, 50.00, null, 0.91327722, 45.66, 5408.26, 5.82, 0.00, null,
+ -5.82, 1000.00);
+ checkInst(model, 86, 86, LocalDate.of(2019, 3, 28), 114, 86, 50.00, 50.00, null, 0.91230305, 45.62, 5364.03, 5.78, 0.00, null,
+ -5.78, 1000.00);
+ checkInst(model, 87, 87, LocalDate.of(2019, 3, 29), 113, 87, 50.00, 50.00, null, 0.91132992, 45.57, 5319.76, 5.73, 0.00, null,
+ -5.73, 1000.00);
+ checkInst(model, 88, 88, LocalDate.of(2019, 3, 30), 112, 88, 50.00, 50.00, null, 0.91035783, 45.52, 5275.44, 5.68, 0.00, null,
+ -5.68, 1000.00);
+ checkInst(model, 89, 89, LocalDate.of(2019, 3, 31), 111, 89, 50.00, 50.00, null, 0.90938677, 45.47, 5231.08, 5.63, 0.00, null,
+ -5.63, 1000.00);
+ checkInst(model, 90, 90, LocalDate.of(2019, 4, 1), 110, 90, 50.00, 50.00, null, 0.90841675, 45.42, 5186.66, 5.59, 0.00, null, -5.59,
+ 1000.00);
+ checkInst(model, 91, 91, LocalDate.of(2019, 4, 2), 109, 91, 50.00, 50.00, null, 0.90744776, 45.37, 5142.20, 5.54, 0.00, null, -5.54,
+ 1000.00);
+ checkInst(model, 92, 92, LocalDate.of(2019, 4, 3), 108, 92, 50.00, 50.00, null, 0.90647981, 45.32, 5097.69, 5.49, 0.00, null, -5.49,
+ 1000.00);
+ checkInst(model, 93, 93, LocalDate.of(2019, 4, 4), 107, 93, 50.00, 50.00, null, 0.90551289, 45.28, 5053.13, 5.44, 0.00, null, -5.44,
+ 1000.00);
+ checkInst(model, 94, 94, LocalDate.of(2019, 4, 5), 106, 94, 50.00, 50.00, null, 0.90454700, 45.23, 5008.53, 5.40, 0.00, null, -5.40,
+ 1000.00);
+ checkInst(model, 95, 95, LocalDate.of(2019, 4, 6), 105, 95, 50.00, 50.00, null, 0.90358215, 45.18, 4963.88, 5.35, 0.00, null, -5.35,
+ 1000.00);
+ checkInst(model, 96, 96, LocalDate.of(2019, 4, 7), 104, 96, 50.00, 50.00, null, 0.90261832, 45.13, 4919.18, 5.30, 0.00, null, -5.30,
+ 1000.00);
+ checkInst(model, 97, 97, LocalDate.of(2019, 4, 8), 103, 97, 50.00, 50.00, null, 0.90165552, 45.08, 4874.43, 5.25, 0.00, null, -5.25,
+ 1000.00);
+ checkInst(model, 98, 98, LocalDate.of(2019, 4, 9), 102, 98, 50.00, 50.00, null, 0.90069374, 45.03, 4829.64, 5.20, 0.00, null, -5.20,
+ 1000.00);
+ checkInst(model, 99, 99, LocalDate.of(2019, 4, 10), 101, 99, 50.00, 50.00, null, 0.89973299, 44.99, 4784.79, 5.16, 0.00, null,
+ -5.16, 1000.00);
+ checkInst(model, 100, 100, LocalDate.of(2019, 4, 11), 100, 100, 50.00, 50.00, null, 0.89877327, 44.94, 4739.90, 5.11, 0.00, null,
+ -5.11, 1000.00);
+ checkInst(model, 101, 101, LocalDate.of(2019, 4, 12), 99, 101, 50.00, 50.00, null, 0.89781457, 44.89, 4694.96, 5.06, 0.00, null,
+ -5.06, 1000.00);
+ checkInst(model, 102, 102, LocalDate.of(2019, 4, 13), 98, 102, 50.00, 50.00, null, 0.89685689, 44.84, 4649.98, 5.01, 0.00, null,
+ -5.01, 1000.00);
+ checkInst(model, 103, 103, LocalDate.of(2019, 4, 14), 97, 103, 50.00, 50.00, null, 0.89590024, 44.80, 4604.94, 4.97, 0.00, null,
+ -4.97, 1000.00);
+ checkInst(model, 104, 104, LocalDate.of(2019, 4, 15), 96, 104, 50.00, 50.00, null, 0.89494460, 44.75, 4559.86, 4.92, 0.00, null,
+ -4.92, 1000.00);
+ checkInst(model, 105, 105, LocalDate.of(2019, 4, 16), 95, 105, 50.00, 50.00, null, 0.89398999, 44.70, 4514.73, 4.87, 0.00, null,
+ -4.87, 1000.00);
+ checkInst(model, 106, 106, LocalDate.of(2019, 4, 17), 94, 106, 50.00, 50.00, null, 0.89303639, 44.65, 4469.55, 4.82, 0.00, null,
+ -4.82, 1000.00);
+ checkInst(model, 107, 107, LocalDate.of(2019, 4, 18), 93, 107, 50.00, 50.00, null, 0.89208381, 44.60, 4424.32, 4.77, 0.00, null,
+ -4.77, 1000.00);
+ checkInst(model, 108, 108, LocalDate.of(2019, 4, 19), 92, 108, 50.00, 50.00, null, 0.89113225, 44.56, 4379.05, 4.72, 0.00, null,
+ -4.72, 1000.00);
+ checkInst(model, 109, 109, LocalDate.of(2019, 4, 20), 91, 109, 50.00, 50.00, null, 0.89018170, 44.51, 4333.72, 4.68, 0.00, null,
+ -4.68, 1000.00);
+ checkInst(model, 110, 110, LocalDate.of(2019, 4, 21), 90, 110, 50.00, 50.00, null, 0.88923216, 44.46, 4288.35, 4.63, 0.00, null,
+ -4.63, 1000.00);
+ checkInst(model, 111, 111, LocalDate.of(2019, 4, 22), 89, 111, 50.00, 50.00, null, 0.88828364, 44.41, 4242.93, 4.58, 0.00, null,
+ -4.58, 1000.00);
+ checkInst(model, 112, 112, LocalDate.of(2019, 4, 23), 88, 112, 50.00, 50.00, null, 0.88733613, 44.37, 4197.46, 4.53, 0.00, null,
+ -4.53, 1000.00);
+ checkInst(model, 113, 113, LocalDate.of(2019, 4, 24), 87, 113, 50.00, 50.00, null, 0.88638963, 44.32, 4151.94, 4.48, 0.00, null,
+ -4.48, 1000.00);
+ checkInst(model, 114, 114, LocalDate.of(2019, 4, 25), 86, 114, 50.00, 50.00, null, 0.88544414, 44.27, 4106.38, 4.43, 0.00, null,
+ -4.43, 1000.00);
+ checkInst(model, 115, 115, LocalDate.of(2019, 4, 26), 85, 115, 50.00, 50.00, null, 0.88449966, 44.22, 4060.76, 4.38, 0.00, null,
+ -4.38, 1000.00);
+ checkInst(model, 116, 116, LocalDate.of(2019, 4, 27), 84, 116, 50.00, 50.00, null, 0.88355619, 44.18, 4015.10, 4.34, 0.00, null,
+ -4.34, 1000.00);
+ checkInst(model, 117, 117, LocalDate.of(2019, 4, 28), 83, 117, 50.00, 50.00, null, 0.88261372, 44.13, 3969.38, 4.29, 0.00, null,
+ -4.29, 1000.00);
+ checkInst(model, 118, 118, LocalDate.of(2019, 4, 29), 82, 118, 50.00, 50.00, null, 0.88167226, 44.08, 3923.62, 4.24, 0.00, null,
+ -4.24, 1000.00);
+ checkInst(model, 119, 119, LocalDate.of(2019, 4, 30), 81, 119, 50.00, 50.00, null, 0.88073180, 44.04, 3877.81, 4.19, 0.00, null,
+ -4.19, 1000.00);
+ checkInst(model, 120, 120, LocalDate.of(2019, 5, 1), 80, 120, 50.00, 50.00, null, 0.87979234, 43.99, 3831.95, 4.14, 0.00, null,
+ -4.14, 1000.00);
+ checkInst(model, 121, 121, LocalDate.of(2019, 5, 2), 79, 121, 50.00, 50.00, null, 0.87885389, 43.94, 3786.04, 4.09, 0.00, null,
+ -4.09, 1000.00);
+ checkInst(model, 122, 122, LocalDate.of(2019, 5, 3), 78, 122, 50.00, 50.00, null, 0.87791644, 43.90, 3740.09, 4.04, 0.00, null,
+ -4.04, 1000.00);
+ checkInst(model, 123, 123, LocalDate.of(2019, 5, 4), 77, 123, 50.00, 50.00, null, 0.87697999, 43.85, 3694.08, 3.99, 0.00, null,
+ -3.99, 1000.00);
+ checkInst(model, 124, 124, LocalDate.of(2019, 5, 5), 76, 124, 50.00, 50.00, null, 0.87604453, 43.80, 3648.03, 3.94, 0.00, null,
+ -3.94, 1000.00);
+ checkInst(model, 125, 125, LocalDate.of(2019, 5, 6), 75, 125, 50.00, 50.00, null, 0.87511008, 43.76, 3601.92, 3.90, 0.00, null,
+ -3.90, 1000.00);
+ checkInst(model, 126, 126, LocalDate.of(2019, 5, 7), 74, 126, 50.00, 50.00, null, 0.87417662, 43.71, 3555.77, 3.85, 0.00, null,
+ -3.85, 1000.00);
+ checkInst(model, 127, 127, LocalDate.of(2019, 5, 8), 73, 127, 50.00, 50.00, null, 0.87324416, 43.66, 3509.56, 3.80, 0.00, null,
+ -3.80, 1000.00);
+ checkInst(model, 128, 128, LocalDate.of(2019, 5, 9), 72, 128, 50.00, 50.00, null, 0.87231269, 43.62, 3463.31, 3.75, 0.00, null,
+ -3.75, 1000.00);
+ checkInst(model, 129, 129, LocalDate.of(2019, 5, 10), 71, 129, 50.00, 50.00, null, 0.87138221, 43.57, 3417.01, 3.70, 0.00, null,
+ -3.70, 1000.00);
+ checkInst(model, 130, 130, LocalDate.of(2019, 5, 11), 70, 130, 50.00, 50.00, null, 0.87045273, 43.52, 3370.66, 3.65, 0.00, null,
+ -3.65, 1000.00);
+ checkInst(model, 131, 131, LocalDate.of(2019, 5, 12), 69, 131, 50.00, 50.00, null, 0.86952424, 43.48, 3324.26, 3.60, 0.00, null,
+ -3.60, 1000.00);
+ checkInst(model, 132, 132, LocalDate.of(2019, 5, 13), 68, 132, 50.00, 50.00, null, 0.86859674, 43.43, 3277.81, 3.55, 0.00, null,
+ -3.55, 1000.00);
+ checkInst(model, 133, 133, LocalDate.of(2019, 5, 14), 67, 133, 50.00, 50.00, null, 0.86767023, 43.38, 3231.31, 3.50, 0.00, null,
+ -3.50, 1000.00);
+ checkInst(model, 134, 134, LocalDate.of(2019, 5, 15), 66, 134, 50.00, 50.00, null, 0.86674471, 43.34, 3184.76, 3.45, 0.00, null,
+ -3.45, 1000.00);
+ checkInst(model, 135, 135, LocalDate.of(2019, 5, 16), 65, 135, 50.00, 50.00, null, 0.86582017, 43.29, 3138.16, 3.40, 0.00, null,
+ -3.40, 1000.00);
+ checkInst(model, 136, 136, LocalDate.of(2019, 5, 17), 64, 136, 50.00, 50.00, null, 0.86489662, 43.24, 3091.51, 3.35, 0.00, null,
+ -3.35, 1000.00);
+ checkInst(model, 137, 137, LocalDate.of(2019, 5, 18), 63, 137, 50.00, 50.00, null, 0.86397406, 43.20, 3044.81, 3.30, 0.00, null,
+ -3.30, 1000.00);
+ checkInst(model, 138, 138, LocalDate.of(2019, 5, 19), 62, 138, 50.00, 50.00, null, 0.86305248, 43.15, 2998.06, 3.25, 0.00, null,
+ -3.25, 1000.00);
+ checkInst(model, 139, 139, LocalDate.of(2019, 5, 20), 61, 139, 50.00, 50.00, null, 0.86213188, 43.11, 2951.26, 3.20, 0.00, null,
+ -3.20, 1000.00);
+ checkInst(model, 140, 140, LocalDate.of(2019, 5, 21), 60, 140, 50.00, 50.00, null, 0.86121227, 43.06, 2904.42, 3.15, 0.00, null,
+ -3.15, 1000.00);
+ checkInst(model, 141, 141, LocalDate.of(2019, 5, 22), 59, 141, 50.00, 50.00, null, 0.86029363, 43.01, 2857.52, 3.10, 0.00, null,
+ -3.10, 1000.00);
+ checkInst(model, 142, 142, LocalDate.of(2019, 5, 23), 58, 142, 50.00, 50.00, null, 0.85937598, 42.97, 2810.57, 3.05, 0.00, null,
+ -3.05, 1000.00);
+ checkInst(model, 143, 143, LocalDate.of(2019, 5, 24), 57, 143, 50.00, 50.00, null, 0.85845930, 42.92, 2763.57, 3.00, 0.00, null,
+ -3.00, 1000.00);
+ checkInst(model, 144, 144, LocalDate.of(2019, 5, 25), 56, 144, 50.00, 50.00, null, 0.85754361, 42.88, 2716.52, 2.95, 0.00, null,
+ -2.95, 1000.00);
+ checkInst(model, 145, 145, LocalDate.of(2019, 5, 26), 55, 145, 50.00, 50.00, null, 0.85662889, 42.83, 2669.42, 2.90, 0.00, null,
+ -2.90, 1000.00);
+ checkInst(model, 146, 146, LocalDate.of(2019, 5, 27), 54, 146, 50.00, 50.00, null, 0.85571514, 42.79, 2622.27, 2.85, 0.00, null,
+ -2.85, 1000.00);
+ checkInst(model, 147, 147, LocalDate.of(2019, 5, 28), 53, 147, 50.00, 50.00, null, 0.85480237, 42.74, 2575.07, 2.80, 0.00, null,
+ -2.80, 1000.00);
+ checkInst(model, 148, 148, LocalDate.of(2019, 5, 29), 52, 148, 50.00, 50.00, null, 0.85389057, 42.69, 2527.82, 2.75, 0.00, null,
+ -2.75, 1000.00);
+ checkInst(model, 149, 149, LocalDate.of(2019, 5, 30), 51, 149, 50.00, 50.00, null, 0.85297975, 42.65, 2480.52, 2.70, 0.00, null,
+ -2.70, 1000.00);
+ checkInst(model, 150, 150, LocalDate.of(2019, 5, 31), 50, 150, 50.00, 50.00, null, 0.85206990, 42.60, 2433.17, 2.65, 0.00, null,
+ -2.65, 1000.00);
+ checkInst(model, 151, 151, LocalDate.of(2019, 6, 1), 49, 151, 50.00, 50.00, null, 0.85116101, 42.56, 2385.77, 2.60, 0.00, null,
+ -2.60, 1000.00);
+ checkInst(model, 152, 152, LocalDate.of(2019, 6, 2), 48, 152, 50.00, 50.00, null, 0.85025310, 42.51, 2338.31, 2.55, 0.00, null,
+ -2.55, 1000.00);
+ checkInst(model, 153, 153, LocalDate.of(2019, 6, 3), 47, 153, 50.00, 50.00, null, 0.84934616, 42.47, 2290.81, 2.50, 0.00, null,
+ -2.50, 1000.00);
+ checkInst(model, 154, 154, LocalDate.of(2019, 6, 4), 46, 154, 50.00, 50.00, null, 0.84844018, 42.42, 2243.26, 2.45, 0.00, null,
+ -2.45, 1000.00);
+ checkInst(model, 155, 155, LocalDate.of(2019, 6, 5), 45, 155, 50.00, 50.00, null, 0.84753517, 42.38, 2195.65, 2.40, 0.00, null,
+ -2.40, 1000.00);
+ checkInst(model, 156, 156, LocalDate.of(2019, 6, 6), 44, 156, 50.00, 50.00, null, 0.84663113, 42.33, 2148.00, 2.34, 0.00, null,
+ -2.34, 1000.00);
+ checkInst(model, 157, 157, LocalDate.of(2019, 6, 7), 43, 157, 50.00, 50.00, null, 0.84572805, 42.29, 2100.29, 2.29, 0.00, null,
+ -2.29, 1000.00);
+ checkInst(model, 158, 158, LocalDate.of(2019, 6, 8), 42, 158, 50.00, 50.00, null, 0.84482593, 42.24, 2052.53, 2.24, 0.00, null,
+ -2.24, 1000.00);
+ checkInst(model, 159, 159, LocalDate.of(2019, 6, 9), 41, 159, 50.00, 50.00, null, 0.84392477, 42.20, 2004.73, 2.19, 0.00, null,
+ -2.19, 1000.00);
+ checkInst(model, 160, 160, LocalDate.of(2019, 6, 10), 40, 160, 50.00, 50.00, null, 0.84302458, 42.15, 1956.87, 2.14, 0.00, null,
+ -2.14, 1000.00);
+ checkInst(model, 161, 161, LocalDate.of(2019, 6, 11), 39, 161, 50.00, 50.00, null, 0.84212535, 42.11, 1908.96, 2.09, 0.00, null,
+ -2.09, 1000.00);
+ checkInst(model, 162, 162, LocalDate.of(2019, 6, 12), 38, 162, 50.00, 50.00, null, 0.84122707, 42.06, 1860.99, 2.04, 0.00, null,
+ -2.04, 1000.00);
+ checkInst(model, 163, 163, LocalDate.of(2019, 6, 13), 37, 163, 50.00, 50.00, null, 0.84032975, 42.02, 1812.98, 1.99, 0.00, null,
+ -1.99, 1000.00);
+ checkInst(model, 164, 164, LocalDate.of(2019, 6, 14), 36, 164, 50.00, 50.00, null, 0.83943340, 41.97, 1764.92, 1.94, 0.00, null,
+ -1.94, 1000.00);
+ checkInst(model, 165, 165, LocalDate.of(2019, 6, 15), 35, 165, 50.00, 50.00, null, 0.83853799, 41.93, 1716.80, 1.88, 0.00, null,
+ -1.88, 1000.00);
+ checkInst(model, 166, 166, LocalDate.of(2019, 6, 16), 34, 166, 50.00, 50.00, null, 0.83764354, 41.88, 1668.64, 1.83, 0.00, null,
+ -1.83, 1000.00);
+ checkInst(model, 167, 167, LocalDate.of(2019, 6, 17), 33, 167, 50.00, 50.00, null, 0.83675005, 41.84, 1620.42, 1.78, 0.00, null,
+ -1.78, 1000.00);
+ checkInst(model, 168, 168, LocalDate.of(2019, 6, 18), 32, 168, 50.00, 50.00, null, 0.83585751, 41.79, 1572.15, 1.73, 0.00, null,
+ -1.73, 1000.00);
+ checkInst(model, 169, 169, LocalDate.of(2019, 6, 19), 31, 169, 50.00, 50.00, null, 0.83496592, 41.75, 1523.83, 1.68, 0.00, null,
+ -1.68, 1000.00);
+ checkInst(model, 170, 170, LocalDate.of(2019, 6, 20), 30, 170, 50.00, 50.00, null, 0.83407528, 41.70, 1475.45, 1.63, 0.00, null,
+ -1.63, 1000.00);
+ checkInst(model, 171, 171, LocalDate.of(2019, 6, 21), 29, 171, 50.00, 50.00, null, 0.83318560, 41.66, 1427.03, 1.58, 0.00, null,
+ -1.58, 1000.00);
+ checkInst(model, 172, 172, LocalDate.of(2019, 6, 22), 28, 172, 50.00, 50.00, null, 0.83229686, 41.61, 1378.55, 1.52, 0.00, null,
+ -1.52, 1000.00);
+ checkInst(model, 173, 173, LocalDate.of(2019, 6, 23), 27, 173, 50.00, 50.00, null, 0.83140907, 41.57, 1330.02, 1.47, 0.00, null,
+ -1.47, 1000.00);
+ checkInst(model, 174, 174, LocalDate.of(2019, 6, 24), 26, 174, 50.00, 50.00, null, 0.83052222, 41.53, 1281.45, 1.42, 0.00, null,
+ -1.42, 1000.00);
+ checkInst(model, 175, 175, LocalDate.of(2019, 6, 25), 25, 175, 50.00, 50.00, null, 0.82963633, 41.48, 1232.81, 1.37, 0.00, null,
+ -1.37, 1000.00);
+ checkInst(model, 176, 176, LocalDate.of(2019, 6, 26), 24, 176, 50.00, 50.00, null, 0.82875137, 41.44, 1184.13, 1.32, 0.00, null,
+ -1.32, 1000.00);
+ checkInst(model, 177, 177, LocalDate.of(2019, 6, 27), 23, 177, 50.00, 50.00, null, 0.82786736, 41.39, 1135.39, 1.26, 0.00, null,
+ -1.26, 1000.00);
+ checkInst(model, 178, 178, LocalDate.of(2019, 6, 28), 22, 178, 50.00, 50.00, null, 0.82698430, 41.35, 1086.61, 1.21, 0.00, null,
+ -1.21, 1000.00);
+ checkInst(model, 179, 179, LocalDate.of(2019, 6, 29), 21, 179, 50.00, 50.00, null, 0.82610217, 41.31, 1037.77, 1.16, 0.00, null,
+ -1.16, 1000.00);
+ checkInst(model, 180, 180, LocalDate.of(2019, 6, 30), 20, 180, 50.00, 50.00, null, 0.82522099, 41.26, 988.88, 1.11, 0.00, null,
+ -1.11, 1000.00);
+ checkInst(model, 181, 181, LocalDate.of(2019, 7, 1), 19, 181, 50.00, 50.00, null, 0.82434075, 41.22, 939.93, 1.06, 0.00, null,
+ -1.06, 1000.00);
+ checkInst(model, 182, 182, LocalDate.of(2019, 7, 2), 18, 182, 50.00, 50.00, null, 0.82346144, 41.17, 890.93, 1.00, 0.00, null,
+ -1.00, 1000.00);
+ checkInst(model, 183, 183, LocalDate.of(2019, 7, 3), 17, 183, 50.00, 50.00, null, 0.82258308, 41.13, 841.89, 0.95, 0.00, null,
+ -0.95, 1000.00);
+ checkInst(model, 184, 184, LocalDate.of(2019, 7, 4), 16, 184, 50.00, 50.00, null, 0.82170565, 41.09, 792.79, 0.90, 0.00, null,
+ -0.90, 1000.00);
+ checkInst(model, 185, 185, LocalDate.of(2019, 7, 5), 15, 185, 50.00, 50.00, null, 0.82082916, 41.04, 743.63, 0.85, 0.00, null,
+ -0.85, 1000.00);
+ checkInst(model, 186, 186, LocalDate.of(2019, 7, 6), 14, 186, 50.00, 50.00, null, 0.81995360, 41.00, 694.43, 0.79, 0.00, null,
+ -0.79, 1000.00);
+ checkInst(model, 187, 187, LocalDate.of(2019, 7, 7), 13, 187, 50.00, 50.00, null, 0.81907897, 40.95, 645.17, 0.74, 0.00, null,
+ -0.74, 1000.00);
+ checkInst(model, 188, 188, LocalDate.of(2019, 7, 8), 12, 188, 50.00, 50.00, null, 0.81820528, 40.91, 595.86, 0.69, 0.00, null,
+ -0.69, 1000.00);
+ checkInst(model, 189, 189, LocalDate.of(2019, 7, 9), 11, 189, 50.00, 50.00, null, 0.81733252, 40.87, 546.49, 0.64, 0.00, null,
+ -0.64, 1000.00);
+ checkInst(model, 190, 190, LocalDate.of(2019, 7, 10), 10, 190, 50.00, 50.00, null, 0.81646069, 40.82, 497.08, 0.58, 0.00, null,
+ -0.58, 1000.00);
+ checkInst(model, 191, 191, LocalDate.of(2019, 7, 11), 9, 191, 50.00, 50.00, null, 0.81558979, 40.78, 447.61, 0.53, 0.00, null,
+ -0.53, 1000.00);
+ checkInst(model, 192, 192, LocalDate.of(2019, 7, 12), 8, 192, 50.00, 50.00, null, 0.81471983, 40.74, 398.08, 0.48, 0.00, null,
+ -0.48, 1000.00);
+ checkInst(model, 193, 193, LocalDate.of(2019, 7, 13), 7, 193, 50.00, 50.00, null, 0.81385078, 40.69, 348.51, 0.43, 0.00, null,
+ -0.43, 1000.00);
+ checkInst(model, 194, 194, LocalDate.of(2019, 7, 14), 6, 194, 50.00, 50.00, null, 0.81298267, 40.65, 298.88, 0.37, 0.00, null,
+ -0.37, 1000.00);
+ checkInst(model, 195, 195, LocalDate.of(2019, 7, 15), 5, 195, 50.00, 50.00, null, 0.81211548, 40.61, 249.20, 0.32, 0.00, null,
+ -0.32, 1000.00);
+ checkInst(model, 196, 196, LocalDate.of(2019, 7, 16), 4, 196, 50.00, 50.00, null, 0.81124922, 40.56, 199.47, 0.27, 0.00, null,
+ -0.27, 1000.00);
+ checkInst(model, 197, 197, LocalDate.of(2019, 7, 17), 3, 197, 50.00, 50.00, null, 0.81038388, 40.52, 149.68, 0.21, 0.00, null,
+ -0.21, 1000.00);
+ checkInst(model, 198, 198, LocalDate.of(2019, 7, 18), 2, 198, 50.00, 50.00, null, 0.80951946, 40.48, 99.84, 0.16, 0.00, null, -0.16,
+ 1000.00);
+ checkInst(model, 199, 199, LocalDate.of(2019, 7, 19), 1, 199, 50.00, 50.00, null, 0.80865597, 40.43, 49.95, 0.11, 0.00, null, -0.11,
+ 1000.00);
+ checkInst(model, 200, 200, LocalDate.of(2019, 7, 20), 0, 200, 50.00, 50.00, null, 0.80779339, 40.39, 0.00, 0.05, 0.00, null, -0.05,
+ 1000.00);
+ }
+
+ @Test
+ void testOnTimePayment_term200_originationFee1000_netDisbursement9000_pay50_50() {
+ final ProjectedAmortizationScheduleModel model = generateModel();
+
+ calculator.applyPayment(model, EXPECTED_DISBURSEMENT_DATE.plusDays(1), new BigDecimal("50"));
+ calculator.applyPayment(model, EXPECTED_DISBURSEMENT_DATE.plusDays(2), new BigDecimal("50"));
+
+ checkInst(model, 0, 0, EXPECTED_DISBURSEMENT_DATE, 202, 0, -9000.00, null, null, 1.00000000, -9000.00, 9000.00, null, null, null,
+ null, 1000.00);
+
+ checkInst(model, 1, 1, LocalDate.of(2019, 1, 2), 201, 0, 50.00, 50.00, 50.00, 1.00000000, 50.00, 8959.61, 9.61, 19.18, 9.61, 0.00,
+ 990.39);
+ checkInst(model, 2, 2, LocalDate.of(2019, 1, 3), 200, 0, 50.00, 50.00, 50.00, 1.00000000, 50.00, 8919.18, 9.57, 9.57, 9.57, 0.00,
+ 980.82);
+ checkInst(model, 3, 3, LocalDate.of(2019, 1, 4), 199, 1, 50.00, 50.00, null, 0.99893332, 49.95, 8878.70, 9.52, 0.00, null, -9.52,
+ 980.82);
+ checkInst(model, 4, 4, LocalDate.of(2019, 1, 5), 198, 2, 50.00, 50.00, null, 0.99786779, 49.89, 8838.18, 9.48, 0.00, null, -9.48,
+ 980.82);
+ checkInst(model, 5, 5, LocalDate.of(2019, 1, 6), 197, 3, 50.00, 50.00, null, 0.99680339, 49.84, 8797.62, 9.44, 0.00, null, -9.44,
+ 980.82);
+ checkInst(model, 6, 6, LocalDate.of(2019, 1, 7), 196, 4, 50.00, 50.00, null, 0.99574012, 49.79, 8757.01, 9.39, 0.00, null, -9.39,
+ 980.82);
+ checkInst(model, 7, 7, LocalDate.of(2019, 1, 8), 195, 5, 50.00, 50.00, null, 0.99467799, 49.73, 8716.36, 9.35, 0.00, null, -9.35,
+ 980.82);
+ checkInst(model, 8, 8, LocalDate.of(2019, 1, 9), 194, 6, 50.00, 50.00, null, 0.99361699, 49.68, 8675.67, 9.31, 0.00, null, -9.31,
+ 980.82);
+ checkInst(model, 9, 9, LocalDate.of(2019, 1, 10), 193, 7, 50.00, 50.00, null, 0.99255712, 49.63, 8634.94, 9.26, 0.00, null, -9.26,
+ 980.82);
+ checkInst(model, 10, 10, LocalDate.of(2019, 1, 11), 192, 8, 50.00, 50.00, null, 0.99149839, 49.57, 8594.16, 9.22, 0.00, null, -9.22,
+ 980.82);
+ checkInst(model, 11, 11, LocalDate.of(2019, 1, 12), 191, 9, 50.00, 50.00, null, 0.99044078, 49.52, 8553.33, 9.18, 0.00, null, -9.18,
+ 980.82);
+ checkInst(model, 12, 12, LocalDate.of(2019, 1, 13), 190, 10, 50.00, 50.00, null, 0.98938430, 49.47, 8512.47, 9.13, 0.00, null,
+ -9.13, 980.82);
+ checkInst(model, 13, 13, LocalDate.of(2019, 1, 14), 189, 11, 50.00, 50.00, null, 0.98832895, 49.42, 8471.56, 9.09, 0.00, null,
+ -9.09, 980.82);
+ checkInst(model, 14, 14, LocalDate.of(2019, 1, 15), 188, 12, 50.00, 50.00, null, 0.98727472, 49.36, 8430.60, 9.05, 0.00, null,
+ -9.05, 980.82);
+ checkInst(model, 15, 15, LocalDate.of(2019, 1, 16), 187, 13, 50.00, 50.00, null, 0.98622162, 49.31, 8389.61, 9.00, 0.00, null,
+ -9.00, 980.82);
+ checkInst(model, 16, 16, LocalDate.of(2019, 1, 17), 186, 14, 50.00, 50.00, null, 0.98516964, 49.26, 8348.56, 8.96, 0.00, null,
+ -8.96, 980.82);
+ checkInst(model, 17, 17, LocalDate.of(2019, 1, 18), 185, 15, 50.00, 50.00, null, 0.98411879, 49.21, 8307.48, 8.91, 0.00, null,
+ -8.91, 980.82);
+ checkInst(model, 18, 18, LocalDate.of(2019, 1, 19), 184, 16, 50.00, 50.00, null, 0.98306905, 49.15, 8266.35, 8.87, 0.00, null,
+ -8.87, 980.82);
+ checkInst(model, 19, 19, LocalDate.of(2019, 1, 20), 183, 17, 50.00, 50.00, null, 0.98202044, 49.10, 8225.18, 8.83, 0.00, null,
+ -8.83, 980.82);
+ checkInst(model, 20, 20, LocalDate.of(2019, 1, 21), 182, 18, 50.00, 50.00, null, 0.98097294, 49.05, 8183.96, 8.78, 0.00, null,
+ -8.78, 980.82);
+ checkInst(model, 21, 21, LocalDate.of(2019, 1, 22), 181, 19, 50.00, 50.00, null, 0.97992656, 49.00, 8142.70, 8.74, 0.00, null,
+ -8.74, 980.82);
+ checkInst(model, 22, 22, LocalDate.of(2019, 1, 23), 180, 20, 50.00, 50.00, null, 0.97888129, 48.94, 8101.39, 8.69, 0.00, null,
+ -8.69, 980.82);
+ checkInst(model, 23, 23, LocalDate.of(2019, 1, 24), 179, 21, 50.00, 50.00, null, 0.97783715, 48.89, 8060.04, 8.65, 0.00, null,
+ -8.65, 980.82);
+ checkInst(model, 24, 24, LocalDate.of(2019, 1, 25), 178, 22, 50.00, 50.00, null, 0.97679411, 48.84, 8018.65, 8.61, 0.00, null,
+ -8.61, 980.82);
+ checkInst(model, 25, 25, LocalDate.of(2019, 1, 26), 177, 23, 50.00, 50.00, null, 0.97575219, 48.79, 7977.21, 8.56, 0.00, null,
+ -8.56, 980.82);
+ checkInst(model, 26, 26, LocalDate.of(2019, 1, 27), 176, 24, 50.00, 50.00, null, 0.97471138, 48.74, 7935.73, 8.52, 0.00, null,
+ -8.52, 980.82);
+ checkInst(model, 27, 27, LocalDate.of(2019, 1, 28), 175, 25, 50.00, 50.00, null, 0.97367168, 48.68, 7894.21, 8.47, 0.00, null,
+ -8.47, 980.82);
+ checkInst(model, 28, 28, LocalDate.of(2019, 1, 29), 174, 26, 50.00, 50.00, null, 0.97263309, 48.63, 7852.63, 8.43, 0.00, null,
+ -8.43, 980.82);
+ checkInst(model, 29, 29, LocalDate.of(2019, 1, 30), 173, 27, 50.00, 50.00, null, 0.97159560, 48.58, 7811.02, 8.39, 0.00, null,
+ -8.39, 980.82);
+ checkInst(model, 30, 30, LocalDate.of(2019, 1, 31), 172, 28, 50.00, 50.00, null, 0.97055922, 48.53, 7769.36, 8.34, 0.00, null,
+ -8.34, 980.82);
+ checkInst(model, 31, 31, LocalDate.of(2019, 2, 1), 171, 29, 50.00, 50.00, null, 0.96952395, 48.48, 7727.66, 8.30, 0.00, null, -8.30,
+ 980.82);
+ checkInst(model, 32, 32, LocalDate.of(2019, 2, 2), 170, 30, 50.00, 50.00, null, 0.96848979, 48.42, 7685.91, 8.25, 0.00, null, -8.25,
+ 980.82);
+ checkInst(model, 33, 33, LocalDate.of(2019, 2, 3), 169, 31, 50.00, 50.00, null, 0.96745672, 48.37, 7644.12, 8.21, 0.00, null, -8.21,
+ 980.82);
+ checkInst(model, 34, 34, LocalDate.of(2019, 2, 4), 168, 32, 50.00, 50.00, null, 0.96642476, 48.32, 7602.28, 8.16, 0.00, null, -8.16,
+ 980.82);
+ checkInst(model, 35, 35, LocalDate.of(2019, 2, 5), 167, 33, 50.00, 50.00, null, 0.96539390, 48.27, 7560.40, 8.12, 0.00, null, -8.12,
+ 980.82);
+ checkInst(model, 36, 36, LocalDate.of(2019, 2, 6), 166, 34, 50.00, 50.00, null, 0.96436413, 48.22, 7518.47, 8.07, 0.00, null, -8.07,
+ 980.82);
+ checkInst(model, 37, 37, LocalDate.of(2019, 2, 7), 165, 35, 50.00, 50.00, null, 0.96333547, 48.17, 7476.50, 8.03, 0.00, null, -8.03,
+ 980.82);
+ checkInst(model, 38, 38, LocalDate.of(2019, 2, 8), 164, 36, 50.00, 50.00, null, 0.96230790, 48.12, 7434.48, 7.98, 0.00, null, -7.98,
+ 980.82);
+ checkInst(model, 39, 39, LocalDate.of(2019, 2, 9), 163, 37, 50.00, 50.00, null, 0.96128143, 48.06, 7392.42, 7.94, 0.00, null, -7.94,
+ 980.82);
+ checkInst(model, 40, 40, LocalDate.of(2019, 2, 10), 162, 38, 50.00, 50.00, null, 0.96025606, 48.01, 7350.31, 7.89, 0.00, null,
+ -7.89, 980.82);
+ checkInst(model, 41, 41, LocalDate.of(2019, 2, 11), 161, 39, 50.00, 50.00, null, 0.95923178, 47.96, 7308.16, 7.85, 0.00, null,
+ -7.85, 980.82);
+ checkInst(model, 42, 42, LocalDate.of(2019, 2, 12), 160, 40, 50.00, 50.00, null, 0.95820859, 47.91, 7265.97, 7.80, 0.00, null,
+ -7.80, 980.82);
+ checkInst(model, 43, 43, LocalDate.of(2019, 2, 13), 159, 41, 50.00, 50.00, null, 0.95718649, 47.86, 7223.72, 7.76, 0.00, null,
+ -7.76, 980.82);
+ checkInst(model, 44, 44, LocalDate.of(2019, 2, 14), 158, 42, 50.00, 50.00, null, 0.95616548, 47.81, 7181.44, 7.71, 0.00, null,
+ -7.71, 980.82);
+ checkInst(model, 45, 45, LocalDate.of(2019, 2, 15), 157, 43, 50.00, 50.00, null, 0.95514557, 47.76, 7139.11, 7.67, 0.00, null,
+ -7.67, 980.82);
+ checkInst(model, 46, 46, LocalDate.of(2019, 2, 16), 156, 44, 50.00, 50.00, null, 0.95412674, 47.71, 7096.73, 7.62, 0.00, null,
+ -7.62, 980.82);
+ checkInst(model, 47, 47, LocalDate.of(2019, 2, 17), 155, 45, 50.00, 50.00, null, 0.95310899, 47.66, 7054.31, 7.58, 0.00, null,
+ -7.58, 980.82);
+ checkInst(model, 48, 48, LocalDate.of(2019, 2, 18), 154, 46, 50.00, 50.00, null, 0.95209233, 47.60, 7011.84, 7.53, 0.00, null,
+ -7.53, 980.82);
+ checkInst(model, 49, 49, LocalDate.of(2019, 2, 19), 153, 47, 50.00, 50.00, null, 0.95107676, 47.55, 6969.33, 7.49, 0.00, null,
+ -7.49, 980.82);
+ checkInst(model, 50, 50, LocalDate.of(2019, 2, 20), 152, 48, 50.00, 50.00, null, 0.95006227, 47.50, 6926.77, 7.44, 0.00, null,
+ -7.44, 980.82);
+ checkInst(model, 51, 51, LocalDate.of(2019, 2, 21), 151, 49, 50.00, 50.00, null, 0.94904886, 47.45, 6884.17, 7.40, 0.00, null,
+ -7.40, 980.82);
+ checkInst(model, 52, 52, LocalDate.of(2019, 2, 22), 150, 50, 50.00, 50.00, null, 0.94803653, 47.40, 6841.52, 7.35, 0.00, null,
+ -7.35, 980.82);
+ checkInst(model, 53, 53, LocalDate.of(2019, 2, 23), 149, 51, 50.00, 50.00, null, 0.94702529, 47.35, 6798.82, 7.31, 0.00, null,
+ -7.31, 980.82);
+ checkInst(model, 54, 54, LocalDate.of(2019, 2, 24), 148, 52, 50.00, 50.00, null, 0.94601512, 47.30, 6756.08, 7.26, 0.00, null,
+ -7.26, 980.82);
+ checkInst(model, 55, 55, LocalDate.of(2019, 2, 25), 147, 53, 50.00, 50.00, null, 0.94500603, 47.25, 6713.30, 7.21, 0.00, null,
+ -7.21, 980.82);
+ checkInst(model, 56, 56, LocalDate.of(2019, 2, 26), 146, 54, 50.00, 50.00, null, 0.94399801, 47.20, 6670.47, 7.17, 0.00, null,
+ -7.17, 980.82);
+ checkInst(model, 57, 57, LocalDate.of(2019, 2, 27), 145, 55, 50.00, 50.00, null, 0.94299107, 47.15, 6627.59, 7.12, 0.00, null,
+ -7.12, 980.82);
+ checkInst(model, 58, 58, LocalDate.of(2019, 2, 28), 144, 56, 50.00, 50.00, null, 0.94198521, 47.10, 6584.67, 7.08, 0.00, null,
+ -7.08, 980.82);
+ checkInst(model, 59, 59, LocalDate.of(2019, 3, 1), 143, 57, 50.00, 50.00, null, 0.94098042, 47.05, 6541.70, 7.03, 0.00, null, -7.03,
+ 980.82);
+ checkInst(model, 60, 60, LocalDate.of(2019, 3, 2), 142, 58, 50.00, 50.00, null, 0.93997669, 47.00, 6498.68, 6.99, 0.00, null, -6.99,
+ 980.82);
+ checkInst(model, 61, 61, LocalDate.of(2019, 3, 3), 141, 59, 50.00, 50.00, null, 0.93897404, 46.95, 6455.62, 6.94, 0.00, null, -6.94,
+ 980.82);
+ checkInst(model, 62, 62, LocalDate.of(2019, 3, 4), 140, 60, 50.00, 50.00, null, 0.93797246, 46.90, 6412.51, 6.89, 0.00, null, -6.89,
+ 980.82);
+ checkInst(model, 63, 63, LocalDate.of(2019, 3, 5), 139, 61, 50.00, 50.00, null, 0.93697195, 46.85, 6369.36, 6.85, 0.00, null, -6.85,
+ 980.82);
+ checkInst(model, 64, 64, LocalDate.of(2019, 3, 6), 138, 62, 50.00, 50.00, null, 0.93597251, 46.80, 6326.16, 6.80, 0.00, null, -6.80,
+ 980.82);
+ checkInst(model, 65, 65, LocalDate.of(2019, 3, 7), 137, 63, 50.00, 50.00, null, 0.93497413, 46.75, 6282.92, 6.76, 0.00, null, -6.76,
+ 980.82);
+ checkInst(model, 66, 66, LocalDate.of(2019, 3, 8), 136, 64, 50.00, 50.00, null, 0.93397681, 46.70, 6239.63, 6.71, 0.00, null, -6.71,
+ 980.82);
+ checkInst(model, 67, 67, LocalDate.of(2019, 3, 9), 135, 65, 50.00, 50.00, null, 0.93298056, 46.65, 6196.29, 6.66, 0.00, null, -6.66,
+ 980.82);
+ checkInst(model, 68, 68, LocalDate.of(2019, 3, 10), 134, 66, 50.00, 50.00, null, 0.93198538, 46.60, 6152.91, 6.62, 0.00, null,
+ -6.62, 980.82);
+ checkInst(model, 69, 69, LocalDate.of(2019, 3, 11), 133, 67, 50.00, 50.00, null, 0.93099125, 46.55, 6109.48, 6.57, 0.00, null,
+ -6.57, 980.82);
+ checkInst(model, 70, 70, LocalDate.of(2019, 3, 12), 132, 68, 50.00, 50.00, null, 0.92999818, 46.50, 6066.00, 6.52, 0.00, null,
+ -6.52, 980.82);
+ checkInst(model, 71, 71, LocalDate.of(2019, 3, 13), 131, 69, 50.00, 50.00, null, 0.92900618, 46.45, 6022.48, 6.48, 0.00, null,
+ -6.48, 980.82);
+ checkInst(model, 72, 72, LocalDate.of(2019, 3, 14), 130, 70, 50.00, 50.00, null, 0.92801523, 46.40, 5978.91, 6.43, 0.00, null,
+ -6.43, 980.82);
+ checkInst(model, 73, 73, LocalDate.of(2019, 3, 15), 129, 71, 50.00, 50.00, null, 0.92702534, 46.35, 5935.29, 6.38, 0.00, null,
+ -6.38, 980.82);
+ checkInst(model, 74, 74, LocalDate.of(2019, 3, 16), 128, 72, 50.00, 50.00, null, 0.92603650, 46.30, 5891.63, 6.34, 0.00, null,
+ -6.34, 980.82);
+ checkInst(model, 75, 75, LocalDate.of(2019, 3, 17), 127, 73, 50.00, 50.00, null, 0.92504872, 46.25, 5847.92, 6.29, 0.00, null,
+ -6.29, 980.82);
+ checkInst(model, 76, 76, LocalDate.of(2019, 3, 18), 126, 74, 50.00, 50.00, null, 0.92406200, 46.20, 5804.17, 6.24, 0.00, null,
+ -6.24, 980.82);
+ checkInst(model, 77, 77, LocalDate.of(2019, 3, 19), 125, 75, 50.00, 50.00, null, 0.92307632, 46.15, 5760.36, 6.20, 0.00, null,
+ -6.20, 980.82);
+ checkInst(model, 78, 78, LocalDate.of(2019, 3, 20), 124, 76, 50.00, 50.00, null, 0.92209170, 46.10, 5716.52, 6.15, 0.00, null,
+ -6.15, 980.82);
+ checkInst(model, 79, 79, LocalDate.of(2019, 3, 21), 123, 77, 50.00, 50.00, null, 0.92110813, 46.06, 5672.62, 6.10, 0.00, null,
+ -6.10, 980.82);
+ checkInst(model, 80, 80, LocalDate.of(2019, 3, 22), 122, 78, 50.00, 50.00, null, 0.92012560, 46.01, 5628.68, 6.06, 0.00, null,
+ -6.06, 980.82);
+ checkInst(model, 81, 81, LocalDate.of(2019, 3, 23), 121, 79, 50.00, 50.00, null, 0.91914413, 45.96, 5584.69, 6.01, 0.00, null,
+ -6.01, 980.82);
+ checkInst(model, 82, 82, LocalDate.of(2019, 3, 24), 120, 80, 50.00, 50.00, null, 0.91816370, 45.91, 5540.65, 5.96, 0.00, null,
+ -5.96, 980.82);
+ checkInst(model, 83, 83, LocalDate.of(2019, 3, 25), 119, 81, 50.00, 50.00, null, 0.91718432, 45.86, 5496.57, 5.92, 0.00, null,
+ -5.92, 980.82);
+ checkInst(model, 84, 84, LocalDate.of(2019, 3, 26), 118, 82, 50.00, 50.00, null, 0.91620598, 45.81, 5452.44, 5.87, 0.00, null,
+ -5.87, 980.82);
+ checkInst(model, 85, 85, LocalDate.of(2019, 3, 27), 117, 83, 50.00, 50.00, null, 0.91522868, 45.76, 5408.26, 5.82, 0.00, null,
+ -5.82, 980.82);
+ checkInst(model, 86, 86, LocalDate.of(2019, 3, 28), 116, 84, 50.00, 50.00, null, 0.91425243, 45.71, 5364.03, 5.78, 0.00, null,
+ -5.78, 980.82);
+ checkInst(model, 87, 87, LocalDate.of(2019, 3, 29), 115, 85, 50.00, 50.00, null, 0.91327722, 45.66, 5319.76, 5.73, 0.00, null,
+ -5.73, 980.82);
+ checkInst(model, 88, 88, LocalDate.of(2019, 3, 30), 114, 86, 50.00, 50.00, null, 0.91230305, 45.62, 5275.44, 5.68, 0.00, null,
+ -5.68, 980.82);
+ checkInst(model, 89, 89, LocalDate.of(2019, 3, 31), 113, 87, 50.00, 50.00, null, 0.91132992, 45.57, 5231.08, 5.63, 0.00, null,
+ -5.63, 980.82);
+ checkInst(model, 90, 90, LocalDate.of(2019, 4, 1), 112, 88, 50.00, 50.00, null, 0.91035783, 45.52, 5186.66, 5.59, 0.00, null, -5.59,
+ 980.82);
+ checkInst(model, 91, 91, LocalDate.of(2019, 4, 2), 111, 89, 50.00, 50.00, null, 0.90938677, 45.47, 5142.20, 5.54, 0.00, null, -5.54,
+ 980.82);
+ checkInst(model, 92, 92, LocalDate.of(2019, 4, 3), 110, 90, 50.00, 50.00, null, 0.90841675, 45.42, 5097.69, 5.49, 0.00, null, -5.49,
+ 980.82);
+ checkInst(model, 93, 93, LocalDate.of(2019, 4, 4), 109, 91, 50.00, 50.00, null, 0.90744776, 45.37, 5053.13, 5.44, 0.00, null, -5.44,
+ 980.82);
+ checkInst(model, 94, 94, LocalDate.of(2019, 4, 5), 108, 92, 50.00, 50.00, null, 0.90647981, 45.32, 5008.53, 5.40, 0.00, null, -5.40,
+ 980.82);
+ checkInst(model, 95, 95, LocalDate.of(2019, 4, 6), 107, 93, 50.00, 50.00, null, 0.90551289, 45.28, 4963.88, 5.35, 0.00, null, -5.35,
+ 980.82);
+ checkInst(model, 96, 96, LocalDate.of(2019, 4, 7), 106, 94, 50.00, 50.00, null, 0.90454700, 45.23, 4919.18, 5.30, 0.00, null, -5.30,
+ 980.82);
+ checkInst(model, 97, 97, LocalDate.of(2019, 4, 8), 105, 95, 50.00, 50.00, null, 0.90358215, 45.18, 4874.43, 5.25, 0.00, null, -5.25,
+ 980.82);
+ checkInst(model, 98, 98, LocalDate.of(2019, 4, 9), 104, 96, 50.00, 50.00, null, 0.90261832, 45.13, 4829.64, 5.20, 0.00, null, -5.20,
+ 980.82);
+ checkInst(model, 99, 99, LocalDate.of(2019, 4, 10), 103, 97, 50.00, 50.00, null, 0.90165552, 45.08, 4784.79, 5.16, 0.00, null,
+ -5.16, 980.82);
+ checkInst(model, 100, 100, LocalDate.of(2019, 4, 11), 102, 98, 50.00, 50.00, null, 0.90069374, 45.03, 4739.90, 5.11, 0.00, null,
+ -5.11, 980.82);
+ checkInst(model, 101, 101, LocalDate.of(2019, 4, 12), 101, 99, 50.00, 50.00, null, 0.89973299, 44.99, 4694.96, 5.06, 0.00, null,
+ -5.06, 980.82);
+ checkInst(model, 102, 102, LocalDate.of(2019, 4, 13), 100, 100, 50.00, 50.00, null, 0.89877327, 44.94, 4649.98, 5.01, 0.00, null,
+ -5.01, 980.82);
+ checkInst(model, 103, 103, LocalDate.of(2019, 4, 14), 99, 101, 50.00, 50.00, null, 0.89781457, 44.89, 4604.94, 4.97, 0.00, null,
+ -4.97, 980.82);
+ checkInst(model, 104, 104, LocalDate.of(2019, 4, 15), 98, 102, 50.00, 50.00, null, 0.89685689, 44.84, 4559.86, 4.92, 0.00, null,
+ -4.92, 980.82);
+ checkInst(model, 105, 105, LocalDate.of(2019, 4, 16), 97, 103, 50.00, 50.00, null, 0.89590024, 44.80, 4514.73, 4.87, 0.00, null,
+ -4.87, 980.82);
+ checkInst(model, 106, 106, LocalDate.of(2019, 4, 17), 96, 104, 50.00, 50.00, null, 0.89494460, 44.75, 4469.55, 4.82, 0.00, null,
+ -4.82, 980.82);
+ checkInst(model, 107, 107, LocalDate.of(2019, 4, 18), 95, 105, 50.00, 50.00, null, 0.89398999, 44.70, 4424.32, 4.77, 0.00, null,
+ -4.77, 980.82);
+ checkInst(model, 108, 108, LocalDate.of(2019, 4, 19), 94, 106, 50.00, 50.00, null, 0.89303639, 44.65, 4379.05, 4.72, 0.00, null,
+ -4.72, 980.82);
+ checkInst(model, 109, 109, LocalDate.of(2019, 4, 20), 93, 107, 50.00, 50.00, null, 0.89208381, 44.60, 4333.72, 4.68, 0.00, null,
+ -4.68, 980.82);
+ checkInst(model, 110, 110, LocalDate.of(2019, 4, 21), 92, 108, 50.00, 50.00, null, 0.89113225, 44.56, 4288.35, 4.63, 0.00, null,
+ -4.63, 980.82);
+ checkInst(model, 111, 111, LocalDate.of(2019, 4, 22), 91, 109, 50.00, 50.00, null, 0.89018170, 44.51, 4242.93, 4.58, 0.00, null,
+ -4.58, 980.82);
+ checkInst(model, 112, 112, LocalDate.of(2019, 4, 23), 90, 110, 50.00, 50.00, null, 0.88923216, 44.46, 4197.46, 4.53, 0.00, null,
+ -4.53, 980.82);
+ checkInst(model, 113, 113, LocalDate.of(2019, 4, 24), 89, 111, 50.00, 50.00, null, 0.88828364, 44.41, 4151.94, 4.48, 0.00, null,
+ -4.48, 980.82);
+ checkInst(model, 114, 114, LocalDate.of(2019, 4, 25), 88, 112, 50.00, 50.00, null, 0.88733613, 44.37, 4106.38, 4.43, 0.00, null,
+ -4.43, 980.82);
+ checkInst(model, 115, 115, LocalDate.of(2019, 4, 26), 87, 113, 50.00, 50.00, null, 0.88638963, 44.32, 4060.76, 4.38, 0.00, null,
+ -4.38, 980.82);
+ checkInst(model, 116, 116, LocalDate.of(2019, 4, 27), 86, 114, 50.00, 50.00, null, 0.88544414, 44.27, 4015.10, 4.34, 0.00, null,
+ -4.34, 980.82);
+ checkInst(model, 117, 117, LocalDate.of(2019, 4, 28), 85, 115, 50.00, 50.00, null, 0.88449966, 44.22, 3969.38, 4.29, 0.00, null,
+ -4.29, 980.82);
+ checkInst(model, 118, 118, LocalDate.of(2019, 4, 29), 84, 116, 50.00, 50.00, null, 0.88355619, 44.18, 3923.62, 4.24, 0.00, null,
+ -4.24, 980.82);
+ checkInst(model, 119, 119, LocalDate.of(2019, 4, 30), 83, 117, 50.00, 50.00, null, 0.88261372, 44.13, 3877.81, 4.19, 0.00, null,
+ -4.19, 980.82);
+ checkInst(model, 120, 120, LocalDate.of(2019, 5, 1), 82, 118, 50.00, 50.00, null, 0.88167226, 44.08, 3831.95, 4.14, 0.00, null,
+ -4.14, 980.82);
+ checkInst(model, 121, 121, LocalDate.of(2019, 5, 2), 81, 119, 50.00, 50.00, null, 0.88073180, 44.04, 3786.04, 4.09, 0.00, null,
+ -4.09, 980.82);
+ checkInst(model, 122, 122, LocalDate.of(2019, 5, 3), 80, 120, 50.00, 50.00, null, 0.87979234, 43.99, 3740.09, 4.04, 0.00, null,
+ -4.04, 980.82);
+ checkInst(model, 123, 123, LocalDate.of(2019, 5, 4), 79, 121, 50.00, 50.00, null, 0.87885389, 43.94, 3694.08, 3.99, 0.00, null,
+ -3.99, 980.82);
+ checkInst(model, 124, 124, LocalDate.of(2019, 5, 5), 78, 122, 50.00, 50.00, null, 0.87791644, 43.90, 3648.03, 3.94, 0.00, null,
+ -3.94, 980.82);
+ checkInst(model, 125, 125, LocalDate.of(2019, 5, 6), 77, 123, 50.00, 50.00, null, 0.87697999, 43.85, 3601.92, 3.90, 0.00, null,
+ -3.90, 980.82);
+ checkInst(model, 126, 126, LocalDate.of(2019, 5, 7), 76, 124, 50.00, 50.00, null, 0.87604453, 43.80, 3555.77, 3.85, 0.00, null,
+ -3.85, 980.82);
+ checkInst(model, 127, 127, LocalDate.of(2019, 5, 8), 75, 125, 50.00, 50.00, null, 0.87511008, 43.76, 3509.56, 3.80, 0.00, null,
+ -3.80, 980.82);
+ checkInst(model, 128, 128, LocalDate.of(2019, 5, 9), 74, 126, 50.00, 50.00, null, 0.87417662, 43.71, 3463.31, 3.75, 0.00, null,
+ -3.75, 980.82);
+ checkInst(model, 129, 129, LocalDate.of(2019, 5, 10), 73, 127, 50.00, 50.00, null, 0.87324416, 43.66, 3417.01, 3.70, 0.00, null,
+ -3.70, 980.82);
+ checkInst(model, 130, 130, LocalDate.of(2019, 5, 11), 72, 128, 50.00, 50.00, null, 0.87231269, 43.62, 3370.66, 3.65, 0.00, null,
+ -3.65, 980.82);
+ checkInst(model, 131, 131, LocalDate.of(2019, 5, 12), 71, 129, 50.00, 50.00, null, 0.87138221, 43.57, 3324.26, 3.60, 0.00, null,
+ -3.60, 980.82);
+ checkInst(model, 132, 132, LocalDate.of(2019, 5, 13), 70, 130, 50.00, 50.00, null, 0.87045273, 43.52, 3277.81, 3.55, 0.00, null,
+ -3.55, 980.82);
+ checkInst(model, 133, 133, LocalDate.of(2019, 5, 14), 69, 131, 50.00, 50.00, null, 0.86952424, 43.48, 3231.31, 3.50, 0.00, null,
+ -3.50, 980.82);
+ checkInst(model, 134, 134, LocalDate.of(2019, 5, 15), 68, 132, 50.00, 50.00, null, 0.86859674, 43.43, 3184.76, 3.45, 0.00, null,
+ -3.45, 980.82);
+ checkInst(model, 135, 135, LocalDate.of(2019, 5, 16), 67, 133, 50.00, 50.00, null, 0.86767023, 43.38, 3138.16, 3.40, 0.00, null,
+ -3.40, 980.82);
+ checkInst(model, 136, 136, LocalDate.of(2019, 5, 17), 66, 134, 50.00, 50.00, null, 0.86674471, 43.34, 3091.51, 3.35, 0.00, null,
+ -3.35, 980.82);
+ checkInst(model, 137, 137, LocalDate.of(2019, 5, 18), 65, 135, 50.00, 50.00, null, 0.86582017, 43.29, 3044.81, 3.30, 0.00, null,
+ -3.30, 980.82);
+ checkInst(model, 138, 138, LocalDate.of(2019, 5, 19), 64, 136, 50.00, 50.00, null, 0.86489662, 43.24, 2998.06, 3.25, 0.00, null,
+ -3.25, 980.82);
+ checkInst(model, 139, 139, LocalDate.of(2019, 5, 20), 63, 137, 50.00, 50.00, null, 0.86397406, 43.20, 2951.26, 3.20, 0.00, null,
+ -3.20, 980.82);
+ checkInst(model, 140, 140, LocalDate.of(2019, 5, 21), 62, 138, 50.00, 50.00, null, 0.86305248, 43.15, 2904.42, 3.15, 0.00, null,
+ -3.15, 980.82);
+ checkInst(model, 141, 141, LocalDate.of(2019, 5, 22), 61, 139, 50.00, 50.00, null, 0.86213188, 43.11, 2857.52, 3.10, 0.00, null,
+ -3.10, 980.82);
+ checkInst(model, 142, 142, LocalDate.of(2019, 5, 23), 60, 140, 50.00, 50.00, null, 0.86121227, 43.06, 2810.57, 3.05, 0.00, null,
+ -3.05, 980.82);
+ checkInst(model, 143, 143, LocalDate.of(2019, 5, 24), 59, 141, 50.00, 50.00, null, 0.86029363, 43.01, 2763.57, 3.00, 0.00, null,
+ -3.00, 980.82);
+ checkInst(model, 144, 144, LocalDate.of(2019, 5, 25), 58, 142, 50.00, 50.00, null, 0.85937598, 42.97, 2716.52, 2.95, 0.00, null,
+ -2.95, 980.82);
+ checkInst(model, 145, 145, LocalDate.of(2019, 5, 26), 57, 143, 50.00, 50.00, null, 0.85845930, 42.92, 2669.42, 2.90, 0.00, null,
+ -2.90, 980.82);
+ checkInst(model, 146, 146, LocalDate.of(2019, 5, 27), 56, 144, 50.00, 50.00, null, 0.85754361, 42.88, 2622.27, 2.85, 0.00, null,
+ -2.85, 980.82);
+ checkInst(model, 147, 147, LocalDate.of(2019, 5, 28), 55, 145, 50.00, 50.00, null, 0.85662889, 42.83, 2575.07, 2.80, 0.00, null,
+ -2.80, 980.82);
+ checkInst(model, 148, 148, LocalDate.of(2019, 5, 29), 54, 146, 50.00, 50.00, null, 0.85571514, 42.79, 2527.82, 2.75, 0.00, null,
+ -2.75, 980.82);
+ checkInst(model, 149, 149, LocalDate.of(2019, 5, 30), 53, 147, 50.00, 50.00, null, 0.85480237, 42.74, 2480.52, 2.70, 0.00, null,
+ -2.70, 980.82);
+ checkInst(model, 150, 150, LocalDate.of(2019, 5, 31), 52, 148, 50.00, 50.00, null, 0.85389057, 42.69, 2433.17, 2.65, 0.00, null,
+ -2.65, 980.82);
+ checkInst(model, 151, 151, LocalDate.of(2019, 6, 1), 51, 149, 50.00, 50.00, null, 0.85297975, 42.65, 2385.77, 2.60, 0.00, null,
+ -2.60, 980.82);
+ checkInst(model, 152, 152, LocalDate.of(2019, 6, 2), 50, 150, 50.00, 50.00, null, 0.85206990, 42.60, 2338.31, 2.55, 0.00, null,
+ -2.55, 980.82);
+ checkInst(model, 153, 153, LocalDate.of(2019, 6, 3), 49, 151, 50.00, 50.00, null, 0.85116101, 42.56, 2290.81, 2.50, 0.00, null,
+ -2.50, 980.82);
+ checkInst(model, 154, 154, LocalDate.of(2019, 6, 4), 48, 152, 50.00, 50.00, null, 0.85025310, 42.51, 2243.26, 2.45, 0.00, null,
+ -2.45, 980.82);
+ checkInst(model, 155, 155, LocalDate.of(2019, 6, 5), 47, 153, 50.00, 50.00, null, 0.84934616, 42.47, 2195.65, 2.40, 0.00, null,
+ -2.40, 980.82);
+ checkInst(model, 156, 156, LocalDate.of(2019, 6, 6), 46, 154, 50.00, 50.00, null, 0.84844018, 42.42, 2148.00, 2.34, 0.00, null,
+ -2.34, 980.82);
+ checkInst(model, 157, 157, LocalDate.of(2019, 6, 7), 45, 155, 50.00, 50.00, null, 0.84753517, 42.38, 2100.29, 2.29, 0.00, null,
+ -2.29, 980.82);
+ checkInst(model, 158, 158, LocalDate.of(2019, 6, 8), 44, 156, 50.00, 50.00, null, 0.84663113, 42.33, 2052.53, 2.24, 0.00, null,
+ -2.24, 980.82);
+ checkInst(model, 159, 159, LocalDate.of(2019, 6, 9), 43, 157, 50.00, 50.00, null, 0.84572805, 42.29, 2004.73, 2.19, 0.00, null,
+ -2.19, 980.82);
+ checkInst(model, 160, 160, LocalDate.of(2019, 6, 10), 42, 158, 50.00, 50.00, null, 0.84482593, 42.24, 1956.87, 2.14, 0.00, null,
+ -2.14, 980.82);
+ checkInst(model, 161, 161, LocalDate.of(2019, 6, 11), 41, 159, 50.00, 50.00, null, 0.84392477, 42.20, 1908.96, 2.09, 0.00, null,
+ -2.09, 980.82);
+ checkInst(model, 162, 162, LocalDate.of(2019, 6, 12), 40, 160, 50.00, 50.00, null, 0.84302458, 42.15, 1860.99, 2.04, 0.00, null,
+ -2.04, 980.82);
+ checkInst(model, 163, 163, LocalDate.of(2019, 6, 13), 39, 161, 50.00, 50.00, null, 0.84212535, 42.11, 1812.98, 1.99, 0.00, null,
+ -1.99, 980.82);
+ checkInst(model, 164, 164, LocalDate.of(2019, 6, 14), 38, 162, 50.00, 50.00, null, 0.84122707, 42.06, 1764.92, 1.94, 0.00, null,
+ -1.94, 980.82);
+ checkInst(model, 165, 165, LocalDate.of(2019, 6, 15), 37, 163, 50.00, 50.00, null, 0.84032975, 42.02, 1716.80, 1.88, 0.00, null,
+ -1.88, 980.82);
+ checkInst(model, 166, 166, LocalDate.of(2019, 6, 16), 36, 164, 50.00, 50.00, null, 0.83943340, 41.97, 1668.64, 1.83, 0.00, null,
+ -1.83, 980.82);
+ checkInst(model, 167, 167, LocalDate.of(2019, 6, 17), 35, 165, 50.00, 50.00, null, 0.83853799, 41.93, 1620.42, 1.78, 0.00, null,
+ -1.78, 980.82);
+ checkInst(model, 168, 168, LocalDate.of(2019, 6, 18), 34, 166, 50.00, 50.00, null, 0.83764354, 41.88, 1572.15, 1.73, 0.00, null,
+ -1.73, 980.82);
+ checkInst(model, 169, 169, LocalDate.of(2019, 6, 19), 33, 167, 50.00, 50.00, null, 0.83675005, 41.84, 1523.83, 1.68, 0.00, null,
+ -1.68, 980.82);
+ checkInst(model, 170, 170, LocalDate.of(2019, 6, 20), 32, 168, 50.00, 50.00, null, 0.83585751, 41.79, 1475.45, 1.63, 0.00, null,
+ -1.63, 980.82);
+ checkInst(model, 171, 171, LocalDate.of(2019, 6, 21), 31, 169, 50.00, 50.00, null, 0.83496592, 41.75, 1427.03, 1.58, 0.00, null,
+ -1.58, 980.82);
+ checkInst(model, 172, 172, LocalDate.of(2019, 6, 22), 30, 170, 50.00, 50.00, null, 0.83407528, 41.70, 1378.55, 1.52, 0.00, null,
+ -1.52, 980.82);
+ checkInst(model, 173, 173, LocalDate.of(2019, 6, 23), 29, 171, 50.00, 50.00, null, 0.83318560, 41.66, 1330.02, 1.47, 0.00, null,
+ -1.47, 980.82);
+ checkInst(model, 174, 174, LocalDate.of(2019, 6, 24), 28, 172, 50.00, 50.00, null, 0.83229686, 41.61, 1281.45, 1.42, 0.00, null,
+ -1.42, 980.82);
+ checkInst(model, 175, 175, LocalDate.of(2019, 6, 25), 27, 173, 50.00, 50.00, null, 0.83140907, 41.57, 1232.81, 1.37, 0.00, null,
+ -1.37, 980.82);
+ checkInst(model, 176, 176, LocalDate.of(2019, 6, 26), 26, 174, 50.00, 50.00, null, 0.83052222, 41.53, 1184.13, 1.32, 0.00, null,
+ -1.32, 980.82);
+ checkInst(model, 177, 177, LocalDate.of(2019, 6, 27), 25, 175, 50.00, 50.00, null, 0.82963633, 41.48, 1135.39, 1.26, 0.00, null,
+ -1.26, 980.82);
+ checkInst(model, 178, 178, LocalDate.of(2019, 6, 28), 24, 176, 50.00, 50.00, null, 0.82875137, 41.44, 1086.61, 1.21, 0.00, null,
+ -1.21, 980.82);
+ checkInst(model, 179, 179, LocalDate.of(2019, 6, 29), 23, 177, 50.00, 50.00, null, 0.82786736, 41.39, 1037.77, 1.16, 0.00, null,
+ -1.16, 980.82);
+ checkInst(model, 180, 180, LocalDate.of(2019, 6, 30), 22, 178, 50.00, 50.00, null, 0.82698430, 41.35, 988.88, 1.11, 0.00, null,
+ -1.11, 980.82);
+ checkInst(model, 181, 181, LocalDate.of(2019, 7, 1), 21, 179, 50.00, 50.00, null, 0.82610217, 41.31, 939.93, 1.06, 0.00, null,
+ -1.06, 980.82);
+ checkInst(model, 182, 182, LocalDate.of(2019, 7, 2), 20, 180, 50.00, 50.00, null, 0.82522099, 41.26, 890.93, 1.00, 0.00, null,
+ -1.00, 980.82);
+ checkInst(model, 183, 183, LocalDate.of(2019, 7, 3), 19, 181, 50.00, 50.00, null, 0.82434075, 41.22, 841.89, 0.95, 0.00, null,
+ -0.95, 980.82);
+ checkInst(model, 184, 184, LocalDate.of(2019, 7, 4), 18, 182, 50.00, 50.00, null, 0.82346144, 41.17, 792.79, 0.90, 0.00, null,
+ -0.90, 980.82);
+ checkInst(model, 185, 185, LocalDate.of(2019, 7, 5), 17, 183, 50.00, 50.00, null, 0.82258308, 41.13, 743.63, 0.85, 0.00, null,
+ -0.85, 980.82);
+ checkInst(model, 186, 186, LocalDate.of(2019, 7, 6), 16, 184, 50.00, 50.00, null, 0.82170565, 41.09, 694.43, 0.79, 0.00, null,
+ -0.79, 980.82);
+ checkInst(model, 187, 187, LocalDate.of(2019, 7, 7), 15, 185, 50.00, 50.00, null, 0.82082916, 41.04, 645.17, 0.74, 0.00, null,
+ -0.74, 980.82);
+ checkInst(model, 188, 188, LocalDate.of(2019, 7, 8), 14, 186, 50.00, 50.00, null, 0.81995360, 41.00, 595.86, 0.69, 0.00, null,
+ -0.69, 980.82);
+ checkInst(model, 189, 189, LocalDate.of(2019, 7, 9), 13, 187, 50.00, 50.00, null, 0.81907897, 40.95, 546.49, 0.64, 0.00, null,
+ -0.64, 980.82);
+ checkInst(model, 190, 190, LocalDate.of(2019, 7, 10), 12, 188, 50.00, 50.00, null, 0.81820528, 40.91, 497.08, 0.58, 0.00, null,
+ -0.58, 980.82);
+ checkInst(model, 191, 191, LocalDate.of(2019, 7, 11), 11, 189, 50.00, 50.00, null, 0.81733252, 40.87, 447.61, 0.53, 0.00, null,
+ -0.53, 980.82);
+ checkInst(model, 192, 192, LocalDate.of(2019, 7, 12), 10, 190, 50.00, 50.00, null, 0.81646069, 40.82, 398.08, 0.48, 0.00, null,
+ -0.48, 980.82);
+ checkInst(model, 193, 193, LocalDate.of(2019, 7, 13), 9, 191, 50.00, 50.00, null, 0.81558979, 40.78, 348.51, 0.43, 0.00, null,
+ -0.43, 980.82);
+ checkInst(model, 194, 194, LocalDate.of(2019, 7, 14), 8, 192, 50.00, 50.00, null, 0.81471983, 40.74, 298.88, 0.37, 0.00, null,
+ -0.37, 980.82);
+ checkInst(model, 195, 195, LocalDate.of(2019, 7, 15), 7, 193, 50.00, 50.00, null, 0.81385078, 40.69, 249.20, 0.32, 0.00, null,
+ -0.32, 980.82);
+ checkInst(model, 196, 196, LocalDate.of(2019, 7, 16), 6, 194, 50.00, 50.00, null, 0.81298267, 40.65, 199.47, 0.27, 0.00, null,
+ -0.27, 980.82);
+ checkInst(model, 197, 197, LocalDate.of(2019, 7, 17), 5, 195, 50.00, 50.00, null, 0.81211548, 40.61, 149.68, 0.21, 0.00, null,
+ -0.21, 980.82);
+ checkInst(model, 198, 198, LocalDate.of(2019, 7, 18), 4, 196, 50.00, 50.00, null, 0.81124922, 40.56, 99.84, 0.16, 0.00, null, -0.16,
+ 980.82);
+ checkInst(model, 199, 199, LocalDate.of(2019, 7, 19), 3, 197, 50.00, 50.00, null, 0.81038388, 40.52, 49.95, 0.11, 0.00, null, -0.11,
+ 980.82);
+ checkInst(model, 200, 200, LocalDate.of(2019, 7, 20), 2, 198, 50.00, 50.00, null, 0.80951946, 40.48, 0.00, 0.05, 0.00, null, -0.05,
+ 980.82);
+
+ }
+
+ @Test
+ void testExcessPayment_term200_originationFee1000_netDisbursement9000_pay70_80() {
+ final ProjectedAmortizationScheduleModel model = generateModel();
+
+ calculator.applyPayment(model, EXPECTED_DISBURSEMENT_DATE.plusDays(1), new BigDecimal("70"));
+ calculator.applyPayment(model, EXPECTED_DISBURSEMENT_DATE.plusDays(2), new BigDecimal("80"));
+
+ checkInst(model, 0, 0, EXPECTED_DISBURSEMENT_DATE, 202, 0, -9000.00, null, null, 1.00000000, -9000.00, 9000.00, null, null, null,
+ null, 1000.00);
+
+ checkInst(model, 1, 1, LocalDate.of(2019, 1, 2), 201, 0, 50.00, 50.00, 70.00, 1.00000000, 70.00, 8959.61, 9.61, 28.70, 13.44, 3.83,
+ 986.56);
+ checkInst(model, 2, 2, LocalDate.of(2019, 1, 3), 200, 0, 50.00, 50.00, 80.00, 1.00000000, 80.00, 8919.18, 9.57, 15.26, 15.26, 5.70,
+ 971.30);
+
+ checkInst(model, 3, 3, LocalDate.of(2019, 1, 4), 199, 1, 50.00, 50.00, null, 0.99893332, 49.95, 8878.70, 9.52, 0.00, null, -9.52,
+ 971.30);
+ checkInst(model, 4, 4, LocalDate.of(2019, 1, 5), 198, 2, 50.00, 50.00, null, 0.99786779, 49.89, 8838.18, 9.48, 0.00, null, -9.48,
+ 971.30);
+ checkInst(model, 5, 5, LocalDate.of(2019, 1, 6), 197, 3, 50.00, 50.00, null, 0.99680339, 49.84, 8797.62, 9.44, 0.00, null, -9.44,
+ 971.30);
+ checkInst(model, 6, 6, LocalDate.of(2019, 1, 7), 196, 4, 50.00, 50.00, null, 0.99574012, 49.79, 8757.01, 9.39, 0.00, null, -9.39,
+ 971.30);
+ checkInst(model, 7, 7, LocalDate.of(2019, 1, 8), 195, 5, 50.00, 50.00, null, 0.99467799, 49.73, 8716.36, 9.35, 0.00, null, -9.35,
+ 971.30);
+ checkInst(model, 8, 8, LocalDate.of(2019, 1, 9), 194, 6, 50.00, 50.00, null, 0.99361699, 49.68, 8675.67, 9.31, 0.00, null, -9.31,
+ 971.30);
+ checkInst(model, 9, 9, LocalDate.of(2019, 1, 10), 193, 7, 50.00, 50.00, null, 0.99255712, 49.63, 8634.94, 9.26, 0.00, null, -9.26,
+ 971.30);
+ checkInst(model, 10, 10, LocalDate.of(2019, 1, 11), 192, 8, 50.00, 50.00, null, 0.99149839, 49.57, 8594.16, 9.22, 0.00, null, -9.22,
+ 971.30);
+ checkInst(model, 11, 11, LocalDate.of(2019, 1, 12), 191, 9, 50.00, 50.00, null, 0.99044078, 49.52, 8553.33, 9.18, 0.00, null, -9.18,
+ 971.30);
+ checkInst(model, 12, 12, LocalDate.of(2019, 1, 13), 190, 10, 50.00, 50.00, null, 0.98938430, 49.47, 8512.47, 9.13, 0.00, null,
+ -9.13, 971.30);
+ checkInst(model, 13, 13, LocalDate.of(2019, 1, 14), 189, 11, 50.00, 50.00, null, 0.98832895, 49.42, 8471.56, 9.09, 0.00, null,
+ -9.09, 971.30);
+ checkInst(model, 14, 14, LocalDate.of(2019, 1, 15), 188, 12, 50.00, 50.00, null, 0.98727472, 49.36, 8430.60, 9.05, 0.00, null,
+ -9.05, 971.30);
+ checkInst(model, 15, 15, LocalDate.of(2019, 1, 16), 187, 13, 50.00, 50.00, null, 0.98622162, 49.31, 8389.61, 9.00, 0.00, null,
+ -9.00, 971.30);
+ checkInst(model, 16, 16, LocalDate.of(2019, 1, 17), 186, 14, 50.00, 50.00, null, 0.98516964, 49.26, 8348.56, 8.96, 0.00, null,
+ -8.96, 971.30);
+ checkInst(model, 17, 17, LocalDate.of(2019, 1, 18), 185, 15, 50.00, 50.00, null, 0.98411879, 49.21, 8307.48, 8.91, 0.00, null,
+ -8.91, 971.30);
+ checkInst(model, 18, 18, LocalDate.of(2019, 1, 19), 184, 16, 50.00, 50.00, null, 0.98306905, 49.15, 8266.35, 8.87, 0.00, null,
+ -8.87, 971.30);
+ checkInst(model, 19, 19, LocalDate.of(2019, 1, 20), 183, 17, 50.00, 50.00, null, 0.98202044, 49.10, 8225.18, 8.83, 0.00, null,
+ -8.83, 971.30);
+ checkInst(model, 20, 20, LocalDate.of(2019, 1, 21), 182, 18, 50.00, 50.00, null, 0.98097294, 49.05, 8183.96, 8.78, 0.00, null,
+ -8.78, 971.30);
+ checkInst(model, 21, 21, LocalDate.of(2019, 1, 22), 181, 19, 50.00, 50.00, null, 0.97992656, 49.00, 8142.70, 8.74, 0.00, null,
+ -8.74, 971.30);
+ checkInst(model, 22, 22, LocalDate.of(2019, 1, 23), 180, 20, 50.00, 50.00, null, 0.97888129, 48.94, 8101.39, 8.69, 0.00, null,
+ -8.69, 971.30);
+ checkInst(model, 23, 23, LocalDate.of(2019, 1, 24), 179, 21, 50.00, 50.00, null, 0.97783715, 48.89, 8060.04, 8.65, 0.00, null,
+ -8.65, 971.30);
+ checkInst(model, 24, 24, LocalDate.of(2019, 1, 25), 178, 22, 50.00, 50.00, null, 0.97679411, 48.84, 8018.65, 8.61, 0.00, null,
+ -8.61, 971.30);
+ checkInst(model, 25, 25, LocalDate.of(2019, 1, 26), 177, 23, 50.00, 50.00, null, 0.97575219, 48.79, 7977.21, 8.56, 0.00, null,
+ -8.56, 971.30);
+ checkInst(model, 26, 26, LocalDate.of(2019, 1, 27), 176, 24, 50.00, 50.00, null, 0.97471138, 48.74, 7935.73, 8.52, 0.00, null,
+ -8.52, 971.30);
+ checkInst(model, 27, 27, LocalDate.of(2019, 1, 28), 175, 25, 50.00, 50.00, null, 0.97367168, 48.68, 7894.21, 8.47, 0.00, null,
+ -8.47, 971.30);
+ checkInst(model, 28, 28, LocalDate.of(2019, 1, 29), 174, 26, 50.00, 50.00, null, 0.97263309, 48.63, 7852.63, 8.43, 0.00, null,
+ -8.43, 971.30);
+ checkInst(model, 29, 29, LocalDate.of(2019, 1, 30), 173, 27, 50.00, 50.00, null, 0.97159560, 48.58, 7811.02, 8.39, 0.00, null,
+ -8.39, 971.30);
+ checkInst(model, 30, 30, LocalDate.of(2019, 1, 31), 172, 28, 50.00, 50.00, null, 0.97055922, 48.53, 7769.36, 8.34, 0.00, null,
+ -8.34, 971.30);
+ checkInst(model, 31, 31, LocalDate.of(2019, 2, 1), 171, 29, 50.00, 50.00, null, 0.96952395, 48.48, 7727.66, 8.30, 0.00, null, -8.30,
+ 971.30);
+ checkInst(model, 32, 32, LocalDate.of(2019, 2, 2), 170, 30, 50.00, 50.00, null, 0.96848979, 48.42, 7685.91, 8.25, 0.00, null, -8.25,
+ 971.30);
+ checkInst(model, 33, 33, LocalDate.of(2019, 2, 3), 169, 31, 50.00, 50.00, null, 0.96745672, 48.37, 7644.12, 8.21, 0.00, null, -8.21,
+ 971.30);
+ checkInst(model, 34, 34, LocalDate.of(2019, 2, 4), 168, 32, 50.00, 50.00, null, 0.96642476, 48.32, 7602.28, 8.16, 0.00, null, -8.16,
+ 971.30);
+ checkInst(model, 35, 35, LocalDate.of(2019, 2, 5), 167, 33, 50.00, 50.00, null, 0.96539390, 48.27, 7560.40, 8.12, 0.00, null, -8.12,
+ 971.30);
+ checkInst(model, 36, 36, LocalDate.of(2019, 2, 6), 166, 34, 50.00, 50.00, null, 0.96436413, 48.22, 7518.47, 8.07, 0.00, null, -8.07,
+ 971.30);
+ checkInst(model, 37, 37, LocalDate.of(2019, 2, 7), 165, 35, 50.00, 50.00, null, 0.96333547, 48.17, 7476.50, 8.03, 0.00, null, -8.03,
+ 971.30);
+ checkInst(model, 38, 38, LocalDate.of(2019, 2, 8), 164, 36, 50.00, 50.00, null, 0.96230790, 48.12, 7434.48, 7.98, 0.00, null, -7.98,
+ 971.30);
+ checkInst(model, 39, 39, LocalDate.of(2019, 2, 9), 163, 37, 50.00, 50.00, null, 0.96128143, 48.06, 7392.42, 7.94, 0.00, null, -7.94,
+ 971.30);
+ checkInst(model, 40, 40, LocalDate.of(2019, 2, 10), 162, 38, 50.00, 50.00, null, 0.96025606, 48.01, 7350.31, 7.89, 0.00, null,
+ -7.89, 971.30);
+ checkInst(model, 41, 41, LocalDate.of(2019, 2, 11), 161, 39, 50.00, 50.00, null, 0.95923178, 47.96, 7308.16, 7.85, 0.00, null,
+ -7.85, 971.30);
+ checkInst(model, 42, 42, LocalDate.of(2019, 2, 12), 160, 40, 50.00, 50.00, null, 0.95820859, 47.91, 7265.97, 7.80, 0.00, null,
+ -7.80, 971.30);
+ checkInst(model, 43, 43, LocalDate.of(2019, 2, 13), 159, 41, 50.00, 50.00, null, 0.95718649, 47.86, 7223.72, 7.76, 0.00, null,
+ -7.76, 971.30);
+ checkInst(model, 44, 44, LocalDate.of(2019, 2, 14), 158, 42, 50.00, 50.00, null, 0.95616548, 47.81, 7181.44, 7.71, 0.00, null,
+ -7.71, 971.30);
+ checkInst(model, 45, 45, LocalDate.of(2019, 2, 15), 157, 43, 50.00, 50.00, null, 0.95514557, 47.76, 7139.11, 7.67, 0.00, null,
+ -7.67, 971.30);
+ checkInst(model, 46, 46, LocalDate.of(2019, 2, 16), 156, 44, 50.00, 50.00, null, 0.95412674, 47.71, 7096.73, 7.62, 0.00, null,
+ -7.62, 971.30);
+ checkInst(model, 47, 47, LocalDate.of(2019, 2, 17), 155, 45, 50.00, 50.00, null, 0.95310899, 47.66, 7054.31, 7.58, 0.00, null,
+ -7.58, 971.30);
+ checkInst(model, 48, 48, LocalDate.of(2019, 2, 18), 154, 46, 50.00, 50.00, null, 0.95209233, 47.60, 7011.84, 7.53, 0.00, null,
+ -7.53, 971.30);
+ checkInst(model, 49, 49, LocalDate.of(2019, 2, 19), 153, 47, 50.00, 50.00, null, 0.95107676, 47.55, 6969.33, 7.49, 0.00, null,
+ -7.49, 971.30);
+ checkInst(model, 50, 50, LocalDate.of(2019, 2, 20), 152, 48, 50.00, 50.00, null, 0.95006227, 47.50, 6926.77, 7.44, 0.00, null,
+ -7.44, 971.30);
+ checkInst(model, 51, 51, LocalDate.of(2019, 2, 21), 151, 49, 50.00, 50.00, null, 0.94904886, 47.45, 6884.17, 7.40, 0.00, null,
+ -7.40, 971.30);
+ checkInst(model, 52, 52, LocalDate.of(2019, 2, 22), 150, 50, 50.00, 50.00, null, 0.94803653, 47.40, 6841.52, 7.35, 0.00, null,
+ -7.35, 971.30);
+ checkInst(model, 53, 53, LocalDate.of(2019, 2, 23), 149, 51, 50.00, 50.00, null, 0.94702529, 47.35, 6798.82, 7.31, 0.00, null,
+ -7.31, 971.30);
+ checkInst(model, 54, 54, LocalDate.of(2019, 2, 24), 148, 52, 50.00, 50.00, null, 0.94601512, 47.30, 6756.08, 7.26, 0.00, null,
+ -7.26, 971.30);
+ checkInst(model, 55, 55, LocalDate.of(2019, 2, 25), 147, 53, 50.00, 50.00, null, 0.94500603, 47.25, 6713.30, 7.21, 0.00, null,
+ -7.21, 971.30);
+ checkInst(model, 56, 56, LocalDate.of(2019, 2, 26), 146, 54, 50.00, 50.00, null, 0.94399801, 47.20, 6670.47, 7.17, 0.00, null,
+ -7.17, 971.30);
+ checkInst(model, 57, 57, LocalDate.of(2019, 2, 27), 145, 55, 50.00, 50.00, null, 0.94299107, 47.15, 6627.59, 7.12, 0.00, null,
+ -7.12, 971.30);
+ checkInst(model, 58, 58, LocalDate.of(2019, 2, 28), 144, 56, 50.00, 50.00, null, 0.94198521, 47.10, 6584.67, 7.08, 0.00, null,
+ -7.08, 971.30);
+ checkInst(model, 59, 59, LocalDate.of(2019, 3, 1), 143, 57, 50.00, 50.00, null, 0.94098042, 47.05, 6541.70, 7.03, 0.00, null, -7.03,
+ 971.30);
+ checkInst(model, 60, 60, LocalDate.of(2019, 3, 2), 142, 58, 50.00, 50.00, null, 0.93997669, 47.00, 6498.68, 6.99, 0.00, null, -6.99,
+ 971.30);
+ checkInst(model, 61, 61, LocalDate.of(2019, 3, 3), 141, 59, 50.00, 50.00, null, 0.93897404, 46.95, 6455.62, 6.94, 0.00, null, -6.94,
+ 971.30);
+ checkInst(model, 62, 62, LocalDate.of(2019, 3, 4), 140, 60, 50.00, 50.00, null, 0.93797246, 46.90, 6412.51, 6.89, 0.00, null, -6.89,
+ 971.30);
+ checkInst(model, 63, 63, LocalDate.of(2019, 3, 5), 139, 61, 50.00, 50.00, null, 0.93697195, 46.85, 6369.36, 6.85, 0.00, null, -6.85,
+ 971.30);
+ checkInst(model, 64, 64, LocalDate.of(2019, 3, 6), 138, 62, 50.00, 50.00, null, 0.93597251, 46.80, 6326.16, 6.80, 0.00, null, -6.80,
+ 971.30);
+ checkInst(model, 65, 65, LocalDate.of(2019, 3, 7), 137, 63, 50.00, 50.00, null, 0.93497413, 46.75, 6282.92, 6.76, 0.00, null, -6.76,
+ 971.30);
+ checkInst(model, 66, 66, LocalDate.of(2019, 3, 8), 136, 64, 50.00, 50.00, null, 0.93397681, 46.70, 6239.63, 6.71, 0.00, null, -6.71,
+ 971.30);
+ checkInst(model, 67, 67, LocalDate.of(2019, 3, 9), 135, 65, 50.00, 50.00, null, 0.93298056, 46.65, 6196.29, 6.66, 0.00, null, -6.66,
+ 971.30);
+ checkInst(model, 68, 68, LocalDate.of(2019, 3, 10), 134, 66, 50.00, 50.00, null, 0.93198538, 46.60, 6152.91, 6.62, 0.00, null,
+ -6.62, 971.30);
+ checkInst(model, 69, 69, LocalDate.of(2019, 3, 11), 133, 67, 50.00, 50.00, null, 0.93099125, 46.55, 6109.48, 6.57, 0.00, null,
+ -6.57, 971.30);
+ checkInst(model, 70, 70, LocalDate.of(2019, 3, 12), 132, 68, 50.00, 50.00, null, 0.92999818, 46.50, 6066.00, 6.52, 0.00, null,
+ -6.52, 971.30);
+ checkInst(model, 71, 71, LocalDate.of(2019, 3, 13), 131, 69, 50.00, 50.00, null, 0.92900618, 46.45, 6022.48, 6.48, 0.00, null,
+ -6.48, 971.30);
+ checkInst(model, 72, 72, LocalDate.of(2019, 3, 14), 130, 70, 50.00, 50.00, null, 0.92801523, 46.40, 5978.91, 6.43, 0.00, null,
+ -6.43, 971.30);
+ checkInst(model, 73, 73, LocalDate.of(2019, 3, 15), 129, 71, 50.00, 50.00, null, 0.92702534, 46.35, 5935.29, 6.38, 0.00, null,
+ -6.38, 971.30);
+ checkInst(model, 74, 74, LocalDate.of(2019, 3, 16), 128, 72, 50.00, 50.00, null, 0.92603650, 46.30, 5891.63, 6.34, 0.00, null,
+ -6.34, 971.30);
+ checkInst(model, 75, 75, LocalDate.of(2019, 3, 17), 127, 73, 50.00, 50.00, null, 0.92504872, 46.25, 5847.92, 6.29, 0.00, null,
+ -6.29, 971.30);
+ checkInst(model, 76, 76, LocalDate.of(2019, 3, 18), 126, 74, 50.00, 50.00, null, 0.92406200, 46.20, 5804.17, 6.24, 0.00, null,
+ -6.24, 971.30);
+ checkInst(model, 77, 77, LocalDate.of(2019, 3, 19), 125, 75, 50.00, 50.00, null, 0.92307632, 46.15, 5760.36, 6.20, 0.00, null,
+ -6.20, 971.30);
+ checkInst(model, 78, 78, LocalDate.of(2019, 3, 20), 124, 76, 50.00, 50.00, null, 0.92209170, 46.10, 5716.52, 6.15, 0.00, null,
+ -6.15, 971.30);
+ checkInst(model, 79, 79, LocalDate.of(2019, 3, 21), 123, 77, 50.00, 50.00, null, 0.92110813, 46.06, 5672.62, 6.10, 0.00, null,
+ -6.10, 971.30);
+ checkInst(model, 80, 80, LocalDate.of(2019, 3, 22), 122, 78, 50.00, 50.00, null, 0.92012560, 46.01, 5628.68, 6.06, 0.00, null,
+ -6.06, 971.30);
+ checkInst(model, 81, 81, LocalDate.of(2019, 3, 23), 121, 79, 50.00, 50.00, null, 0.91914413, 45.96, 5584.69, 6.01, 0.00, null,
+ -6.01, 971.30);
+ checkInst(model, 82, 82, LocalDate.of(2019, 3, 24), 120, 80, 50.00, 50.00, null, 0.91816370, 45.91, 5540.65, 5.96, 0.00, null,
+ -5.96, 971.30);
+ checkInst(model, 83, 83, LocalDate.of(2019, 3, 25), 119, 81, 50.00, 50.00, null, 0.91718432, 45.86, 5496.57, 5.92, 0.00, null,
+ -5.92, 971.30);
+ checkInst(model, 84, 84, LocalDate.of(2019, 3, 26), 118, 82, 50.00, 50.00, null, 0.91620598, 45.81, 5452.44, 5.87, 0.00, null,
+ -5.87, 971.30);
+ checkInst(model, 85, 85, LocalDate.of(2019, 3, 27), 117, 83, 50.00, 50.00, null, 0.91522868, 45.76, 5408.26, 5.82, 0.00, null,
+ -5.82, 971.30);
+ checkInst(model, 86, 86, LocalDate.of(2019, 3, 28), 116, 84, 50.00, 50.00, null, 0.91425243, 45.71, 5364.03, 5.78, 0.00, null,
+ -5.78, 971.30);
+ checkInst(model, 87, 87, LocalDate.of(2019, 3, 29), 115, 85, 50.00, 50.00, null, 0.91327722, 45.66, 5319.76, 5.73, 0.00, null,
+ -5.73, 971.30);
+ checkInst(model, 88, 88, LocalDate.of(2019, 3, 30), 114, 86, 50.00, 50.00, null, 0.91230305, 45.62, 5275.44, 5.68, 0.00, null,
+ -5.68, 971.30);
+ checkInst(model, 89, 89, LocalDate.of(2019, 3, 31), 113, 87, 50.00, 50.00, null, 0.91132992, 45.57, 5231.08, 5.63, 0.00, null,
+ -5.63, 971.30);
+ checkInst(model, 90, 90, LocalDate.of(2019, 4, 1), 112, 88, 50.00, 50.00, null, 0.91035783, 45.52, 5186.66, 5.59, 0.00, null, -5.59,
+ 971.30);
+ checkInst(model, 91, 91, LocalDate.of(2019, 4, 2), 111, 89, 50.00, 50.00, null, 0.90938677, 45.47, 5142.20, 5.54, 0.00, null, -5.54,
+ 971.30);
+ checkInst(model, 92, 92, LocalDate.of(2019, 4, 3), 110, 90, 50.00, 50.00, null, 0.90841675, 45.42, 5097.69, 5.49, 0.00, null, -5.49,
+ 971.30);
+ checkInst(model, 93, 93, LocalDate.of(2019, 4, 4), 109, 91, 50.00, 50.00, null, 0.90744776, 45.37, 5053.13, 5.44, 0.00, null, -5.44,
+ 971.30);
+ checkInst(model, 94, 94, LocalDate.of(2019, 4, 5), 108, 92, 50.00, 50.00, null, 0.90647981, 45.32, 5008.53, 5.40, 0.00, null, -5.40,
+ 971.30);
+ checkInst(model, 95, 95, LocalDate.of(2019, 4, 6), 107, 93, 50.00, 50.00, null, 0.90551289, 45.28, 4963.88, 5.35, 0.00, null, -5.35,
+ 971.30);
+ checkInst(model, 96, 96, LocalDate.of(2019, 4, 7), 106, 94, 50.00, 50.00, null, 0.90454700, 45.23, 4919.18, 5.30, 0.00, null, -5.30,
+ 971.30);
+ checkInst(model, 97, 97, LocalDate.of(2019, 4, 8), 105, 95, 50.00, 50.00, null, 0.90358215, 45.18, 4874.43, 5.25, 0.00, null, -5.25,
+ 971.30);
+ checkInst(model, 98, 98, LocalDate.of(2019, 4, 9), 104, 96, 50.00, 50.00, null, 0.90261832, 45.13, 4829.64, 5.20, 0.00, null, -5.20,
+ 971.30);
+ checkInst(model, 99, 99, LocalDate.of(2019, 4, 10), 103, 97, 50.00, 50.00, null, 0.90165552, 45.08, 4784.79, 5.16, 0.00, null,
+ -5.16, 971.30);
+ checkInst(model, 100, 100, LocalDate.of(2019, 4, 11), 102, 98, 50.00, 50.00, null, 0.90069374, 45.03, 4739.90, 5.11, 0.00, null,
+ -5.11, 971.30);
+ checkInst(model, 101, 101, LocalDate.of(2019, 4, 12), 101, 99, 50.00, 50.00, null, 0.89973299, 44.99, 4694.96, 5.06, 0.00, null,
+ -5.06, 971.30);
+ checkInst(model, 102, 102, LocalDate.of(2019, 4, 13), 100, 100, 50.00, 50.00, null, 0.89877327, 44.94, 4649.98, 5.01, 0.00, null,
+ -5.01, 971.30);
+ checkInst(model, 103, 103, LocalDate.of(2019, 4, 14), 99, 101, 50.00, 50.00, null, 0.89781457, 44.89, 4604.94, 4.97, 0.00, null,
+ -4.97, 971.30);
+ checkInst(model, 104, 104, LocalDate.of(2019, 4, 15), 98, 102, 50.00, 50.00, null, 0.89685689, 44.84, 4559.86, 4.92, 0.00, null,
+ -4.92, 971.30);
+ checkInst(model, 105, 105, LocalDate.of(2019, 4, 16), 97, 103, 50.00, 50.00, null, 0.89590024, 44.80, 4514.73, 4.87, 0.00, null,
+ -4.87, 971.30);
+ checkInst(model, 106, 106, LocalDate.of(2019, 4, 17), 96, 104, 50.00, 50.00, null, 0.89494460, 44.75, 4469.55, 4.82, 0.00, null,
+ -4.82, 971.30);
+ checkInst(model, 107, 107, LocalDate.of(2019, 4, 18), 95, 105, 50.00, 50.00, null, 0.89398999, 44.70, 4424.32, 4.77, 0.00, null,
+ -4.77, 971.30);
+ checkInst(model, 108, 108, LocalDate.of(2019, 4, 19), 94, 106, 50.00, 50.00, null, 0.89303639, 44.65, 4379.05, 4.72, 0.00, null,
+ -4.72, 971.30);
+ checkInst(model, 109, 109, LocalDate.of(2019, 4, 20), 93, 107, 50.00, 50.00, null, 0.89208381, 44.60, 4333.72, 4.68, 0.00, null,
+ -4.68, 971.30);
+ checkInst(model, 110, 110, LocalDate.of(2019, 4, 21), 92, 108, 50.00, 50.00, null, 0.89113225, 44.56, 4288.35, 4.63, 0.00, null,
+ -4.63, 971.30);
+ checkInst(model, 111, 111, LocalDate.of(2019, 4, 22), 91, 109, 50.00, 50.00, null, 0.89018170, 44.51, 4242.93, 4.58, 0.00, null,
+ -4.58, 971.30);
+ checkInst(model, 112, 112, LocalDate.of(2019, 4, 23), 90, 110, 50.00, 50.00, null, 0.88923216, 44.46, 4197.46, 4.53, 0.00, null,
+ -4.53, 971.30);
+ checkInst(model, 113, 113, LocalDate.of(2019, 4, 24), 89, 111, 50.00, 50.00, null, 0.88828364, 44.41, 4151.94, 4.48, 0.00, null,
+ -4.48, 971.30);
+ checkInst(model, 114, 114, LocalDate.of(2019, 4, 25), 88, 112, 50.00, 50.00, null, 0.88733613, 44.37, 4106.38, 4.43, 0.00, null,
+ -4.43, 971.30);
+ checkInst(model, 115, 115, LocalDate.of(2019, 4, 26), 87, 113, 50.00, 50.00, null, 0.88638963, 44.32, 4060.76, 4.38, 0.00, null,
+ -4.38, 971.30);
+ checkInst(model, 116, 116, LocalDate.of(2019, 4, 27), 86, 114, 50.00, 50.00, null, 0.88544414, 44.27, 4015.10, 4.34, 0.00, null,
+ -4.34, 971.30);
+ checkInst(model, 117, 117, LocalDate.of(2019, 4, 28), 85, 115, 50.00, 50.00, null, 0.88449966, 44.22, 3969.38, 4.29, 0.00, null,
+ -4.29, 971.30);
+ checkInst(model, 118, 118, LocalDate.of(2019, 4, 29), 84, 116, 50.00, 50.00, null, 0.88355619, 44.18, 3923.62, 4.24, 0.00, null,
+ -4.24, 971.30);
+ checkInst(model, 119, 119, LocalDate.of(2019, 4, 30), 83, 117, 50.00, 50.00, null, 0.88261372, 44.13, 3877.81, 4.19, 0.00, null,
+ -4.19, 971.30);
+ checkInst(model, 120, 120, LocalDate.of(2019, 5, 1), 82, 118, 50.00, 50.00, null, 0.88167226, 44.08, 3831.95, 4.14, 0.00, null,
+ -4.14, 971.30);
+ checkInst(model, 121, 121, LocalDate.of(2019, 5, 2), 81, 119, 50.00, 50.00, null, 0.88073180, 44.04, 3786.04, 4.09, 0.00, null,
+ -4.09, 971.30);
+ checkInst(model, 122, 122, LocalDate.of(2019, 5, 3), 80, 120, 50.00, 50.00, null, 0.87979234, 43.99, 3740.09, 4.04, 0.00, null,
+ -4.04, 971.30);
+ checkInst(model, 123, 123, LocalDate.of(2019, 5, 4), 79, 121, 50.00, 50.00, null, 0.87885389, 43.94, 3694.08, 3.99, 0.00, null,
+ -3.99, 971.30);
+ checkInst(model, 124, 124, LocalDate.of(2019, 5, 5), 78, 122, 50.00, 50.00, null, 0.87791644, 43.90, 3648.03, 3.94, 0.00, null,
+ -3.94, 971.30);
+ checkInst(model, 125, 125, LocalDate.of(2019, 5, 6), 77, 123, 50.00, 50.00, null, 0.87697999, 43.85, 3601.92, 3.90, 0.00, null,
+ -3.90, 971.30);
+ checkInst(model, 126, 126, LocalDate.of(2019, 5, 7), 76, 124, 50.00, 50.00, null, 0.87604453, 43.80, 3555.77, 3.85, 0.00, null,
+ -3.85, 971.30);
+ checkInst(model, 127, 127, LocalDate.of(2019, 5, 8), 75, 125, 50.00, 50.00, null, 0.87511008, 43.76, 3509.56, 3.80, 0.00, null,
+ -3.80, 971.30);
+ checkInst(model, 128, 128, LocalDate.of(2019, 5, 9), 74, 126, 50.00, 50.00, null, 0.87417662, 43.71, 3463.31, 3.75, 0.00, null,
+ -3.75, 971.30);
+ checkInst(model, 129, 129, LocalDate.of(2019, 5, 10), 73, 127, 50.00, 50.00, null, 0.87324416, 43.66, 3417.01, 3.70, 0.00, null,
+ -3.70, 971.30);
+ checkInst(model, 130, 130, LocalDate.of(2019, 5, 11), 72, 128, 50.00, 50.00, null, 0.87231269, 43.62, 3370.66, 3.65, 0.00, null,
+ -3.65, 971.30);
+ checkInst(model, 131, 131, LocalDate.of(2019, 5, 12), 71, 129, 50.00, 50.00, null, 0.87138221, 43.57, 3324.26, 3.60, 0.00, null,
+ -3.60, 971.30);
+ checkInst(model, 132, 132, LocalDate.of(2019, 5, 13), 70, 130, 50.00, 50.00, null, 0.87045273, 43.52, 3277.81, 3.55, 0.00, null,
+ -3.55, 971.30);
+ checkInst(model, 133, 133, LocalDate.of(2019, 5, 14), 69, 131, 50.00, 50.00, null, 0.86952424, 43.48, 3231.31, 3.50, 0.00, null,
+ -3.50, 971.30);
+ checkInst(model, 134, 134, LocalDate.of(2019, 5, 15), 68, 132, 50.00, 50.00, null, 0.86859674, 43.43, 3184.76, 3.45, 0.00, null,
+ -3.45, 971.30);
+ checkInst(model, 135, 135, LocalDate.of(2019, 5, 16), 67, 133, 50.00, 50.00, null, 0.86767023, 43.38, 3138.16, 3.40, 0.00, null,
+ -3.40, 971.30);
+ checkInst(model, 136, 136, LocalDate.of(2019, 5, 17), 66, 134, 50.00, 50.00, null, 0.86674471, 43.34, 3091.51, 3.35, 0.00, null,
+ -3.35, 971.30);
+ checkInst(model, 137, 137, LocalDate.of(2019, 5, 18), 65, 135, 50.00, 50.00, null, 0.86582017, 43.29, 3044.81, 3.30, 0.00, null,
+ -3.30, 971.30);
+ checkInst(model, 138, 138, LocalDate.of(2019, 5, 19), 64, 136, 50.00, 50.00, null, 0.86489662, 43.24, 2998.06, 3.25, 0.00, null,
+ -3.25, 971.30);
+ checkInst(model, 139, 139, LocalDate.of(2019, 5, 20), 63, 137, 50.00, 50.00, null, 0.86397406, 43.20, 2951.26, 3.20, 0.00, null,
+ -3.20, 971.30);
+ checkInst(model, 140, 140, LocalDate.of(2019, 5, 21), 62, 138, 50.00, 50.00, null, 0.86305248, 43.15, 2904.42, 3.15, 0.00, null,
+ -3.15, 971.30);
+ checkInst(model, 141, 141, LocalDate.of(2019, 5, 22), 61, 139, 50.00, 50.00, null, 0.86213188, 43.11, 2857.52, 3.10, 0.00, null,
+ -3.10, 971.30);
+ checkInst(model, 142, 142, LocalDate.of(2019, 5, 23), 60, 140, 50.00, 50.00, null, 0.86121227, 43.06, 2810.57, 3.05, 0.00, null,
+ -3.05, 971.30);
+ checkInst(model, 143, 143, LocalDate.of(2019, 5, 24), 59, 141, 50.00, 50.00, null, 0.86029363, 43.01, 2763.57, 3.00, 0.00, null,
+ -3.00, 971.30);
+ checkInst(model, 144, 144, LocalDate.of(2019, 5, 25), 58, 142, 50.00, 50.00, null, 0.85937598, 42.97, 2716.52, 2.95, 0.00, null,
+ -2.95, 971.30);
+ checkInst(model, 145, 145, LocalDate.of(2019, 5, 26), 57, 143, 50.00, 50.00, null, 0.85845930, 42.92, 2669.42, 2.90, 0.00, null,
+ -2.90, 971.30);
+ checkInst(model, 146, 146, LocalDate.of(2019, 5, 27), 56, 144, 50.00, 50.00, null, 0.85754361, 42.88, 2622.27, 2.85, 0.00, null,
+ -2.85, 971.30);
+ checkInst(model, 147, 147, LocalDate.of(2019, 5, 28), 55, 145, 50.00, 50.00, null, 0.85662889, 42.83, 2575.07, 2.80, 0.00, null,
+ -2.80, 971.30);
+ checkInst(model, 148, 148, LocalDate.of(2019, 5, 29), 54, 146, 50.00, 50.00, null, 0.85571514, 42.79, 2527.82, 2.75, 0.00, null,
+ -2.75, 971.30);
+ checkInst(model, 149, 149, LocalDate.of(2019, 5, 30), 53, 147, 50.00, 50.00, null, 0.85480237, 42.74, 2480.52, 2.70, 0.00, null,
+ -2.70, 971.30);
+ checkInst(model, 150, 150, LocalDate.of(2019, 5, 31), 52, 148, 50.00, 50.00, null, 0.85389057, 42.69, 2433.17, 2.65, 0.00, null,
+ -2.65, 971.30);
+ checkInst(model, 151, 151, LocalDate.of(2019, 6, 1), 51, 149, 50.00, 50.00, null, 0.85297975, 42.65, 2385.77, 2.60, 0.00, null,
+ -2.60, 971.30);
+ checkInst(model, 152, 152, LocalDate.of(2019, 6, 2), 50, 150, 50.00, 50.00, null, 0.85206990, 42.60, 2338.31, 2.55, 0.00, null,
+ -2.55, 971.30);
+ checkInst(model, 153, 153, LocalDate.of(2019, 6, 3), 49, 151, 50.00, 50.00, null, 0.85116101, 42.56, 2290.81, 2.50, 0.00, null,
+ -2.50, 971.30);
+ checkInst(model, 154, 154, LocalDate.of(2019, 6, 4), 48, 152, 50.00, 50.00, null, 0.85025310, 42.51, 2243.26, 2.45, 0.00, null,
+ -2.45, 971.30);
+ checkInst(model, 155, 155, LocalDate.of(2019, 6, 5), 47, 153, 50.00, 50.00, null, 0.84934616, 42.47, 2195.65, 2.40, 0.00, null,
+ -2.40, 971.30);
+ checkInst(model, 156, 156, LocalDate.of(2019, 6, 6), 46, 154, 50.00, 50.00, null, 0.84844018, 42.42, 2148.00, 2.34, 0.00, null,
+ -2.34, 971.30);
+ checkInst(model, 157, 157, LocalDate.of(2019, 6, 7), 45, 155, 50.00, 50.00, null, 0.84753517, 42.38, 2100.29, 2.29, 0.00, null,
+ -2.29, 971.30);
+ checkInst(model, 158, 158, LocalDate.of(2019, 6, 8), 44, 156, 50.00, 50.00, null, 0.84663113, 42.33, 2052.53, 2.24, 0.00, null,
+ -2.24, 971.30);
+ checkInst(model, 159, 159, LocalDate.of(2019, 6, 9), 43, 157, 50.00, 50.00, null, 0.84572805, 42.29, 2004.73, 2.19, 0.00, null,
+ -2.19, 971.30);
+ checkInst(model, 160, 160, LocalDate.of(2019, 6, 10), 42, 158, 50.00, 50.00, null, 0.84482593, 42.24, 1956.87, 2.14, 0.00, null,
+ -2.14, 971.30);
+ checkInst(model, 161, 161, LocalDate.of(2019, 6, 11), 41, 159, 50.00, 50.00, null, 0.84392477, 42.20, 1908.96, 2.09, 0.00, null,
+ -2.09, 971.30);
+ checkInst(model, 162, 162, LocalDate.of(2019, 6, 12), 40, 160, 50.00, 50.00, null, 0.84302458, 42.15, 1860.99, 2.04, 0.00, null,
+ -2.04, 971.30);
+ checkInst(model, 163, 163, LocalDate.of(2019, 6, 13), 39, 161, 50.00, 50.00, null, 0.84212535, 42.11, 1812.98, 1.99, 0.00, null,
+ -1.99, 971.30);
+ checkInst(model, 164, 164, LocalDate.of(2019, 6, 14), 38, 162, 50.00, 50.00, null, 0.84122707, 42.06, 1764.92, 1.94, 0.00, null,
+ -1.94, 971.30);
+ checkInst(model, 165, 165, LocalDate.of(2019, 6, 15), 37, 163, 50.00, 50.00, null, 0.84032975, 42.02, 1716.80, 1.88, 0.00, null,
+ -1.88, 971.30);
+ checkInst(model, 166, 166, LocalDate.of(2019, 6, 16), 36, 164, 50.00, 50.00, null, 0.83943340, 41.97, 1668.64, 1.83, 0.00, null,
+ -1.83, 971.30);
+ checkInst(model, 167, 167, LocalDate.of(2019, 6, 17), 35, 165, 50.00, 50.00, null, 0.83853799, 41.93, 1620.42, 1.78, 0.00, null,
+ -1.78, 971.30);
+ checkInst(model, 168, 168, LocalDate.of(2019, 6, 18), 34, 166, 50.00, 50.00, null, 0.83764354, 41.88, 1572.15, 1.73, 0.00, null,
+ -1.73, 971.30);
+ checkInst(model, 169, 169, LocalDate.of(2019, 6, 19), 33, 167, 50.00, 50.00, null, 0.83675005, 41.84, 1523.83, 1.68, 0.00, null,
+ -1.68, 971.30);
+ checkInst(model, 170, 170, LocalDate.of(2019, 6, 20), 32, 168, 50.00, 50.00, null, 0.83585751, 41.79, 1475.45, 1.63, 0.00, null,
+ -1.63, 971.30);
+ checkInst(model, 171, 171, LocalDate.of(2019, 6, 21), 31, 169, 50.00, 50.00, null, 0.83496592, 41.75, 1427.03, 1.58, 0.00, null,
+ -1.58, 971.30);
+ checkInst(model, 172, 172, LocalDate.of(2019, 6, 22), 30, 170, 50.00, 50.00, null, 0.83407528, 41.70, 1378.55, 1.52, 0.00, null,
+ -1.52, 971.30);
+ checkInst(model, 173, 173, LocalDate.of(2019, 6, 23), 29, 171, 50.00, 50.00, null, 0.83318560, 41.66, 1330.02, 1.47, 0.00, null,
+ -1.47, 971.30);
+ checkInst(model, 174, 174, LocalDate.of(2019, 6, 24), 28, 172, 50.00, 50.00, null, 0.83229686, 41.61, 1281.45, 1.42, 0.00, null,
+ -1.42, 971.30);
+ checkInst(model, 175, 175, LocalDate.of(2019, 6, 25), 27, 173, 50.00, 50.00, null, 0.83140907, 41.57, 1232.81, 1.37, 0.00, null,
+ -1.37, 971.30);
+ checkInst(model, 176, 176, LocalDate.of(2019, 6, 26), 26, 174, 50.00, 50.00, null, 0.83052222, 41.53, 1184.13, 1.32, 0.00, null,
+ -1.32, 971.30);
+ checkInst(model, 177, 177, LocalDate.of(2019, 6, 27), 25, 175, 50.00, 50.00, null, 0.82963633, 41.48, 1135.39, 1.26, 0.00, null,
+ -1.26, 971.30);
+ checkInst(model, 178, 178, LocalDate.of(2019, 6, 28), 24, 176, 50.00, 50.00, null, 0.82875137, 41.44, 1086.61, 1.21, 0.00, null,
+ -1.21, 971.30);
+ checkInst(model, 179, 179, LocalDate.of(2019, 6, 29), 23, 177, 50.00, 50.00, null, 0.82786736, 41.39, 1037.77, 1.16, 0.00, null,
+ -1.16, 971.30);
+ checkInst(model, 180, 180, LocalDate.of(2019, 6, 30), 22, 178, 50.00, 50.00, null, 0.82698430, 41.35, 988.88, 1.11, 0.00, null,
+ -1.11, 971.30);
+ checkInst(model, 181, 181, LocalDate.of(2019, 7, 1), 21, 179, 50.00, 50.00, null, 0.82610217, 41.31, 939.93, 1.06, 0.00, null,
+ -1.06, 971.30);
+ checkInst(model, 182, 182, LocalDate.of(2019, 7, 2), 20, 180, 50.00, 50.00, null, 0.82522099, 41.26, 890.93, 1.00, 0.00, null,
+ -1.00, 971.30);
+ checkInst(model, 183, 183, LocalDate.of(2019, 7, 3), 19, 181, 50.00, 50.00, null, 0.82434075, 41.22, 841.89, 0.95, 0.00, null,
+ -0.95, 971.30);
+ checkInst(model, 184, 184, LocalDate.of(2019, 7, 4), 18, 182, 50.00, 50.00, null, 0.82346144, 41.17, 792.79, 0.90, 0.00, null,
+ -0.90, 971.30);
+ checkInst(model, 185, 185, LocalDate.of(2019, 7, 5), 17, 183, 50.00, 50.00, null, 0.82258308, 41.13, 743.63, 0.85, 0.00, null,
+ -0.85, 971.30);
+ checkInst(model, 186, 186, LocalDate.of(2019, 7, 6), 16, 184, 50.00, 50.00, null, 0.82170565, 41.09, 694.43, 0.79, 0.00, null,
+ -0.79, 971.30);
+ checkInst(model, 187, 187, LocalDate.of(2019, 7, 7), 15, 185, 50.00, 50.00, null, 0.82082916, 41.04, 645.17, 0.74, 0.00, null,
+ -0.74, 971.30);
+ checkInst(model, 188, 188, LocalDate.of(2019, 7, 8), 14, 186, 50.00, 50.00, null, 0.81995360, 41.00, 595.86, 0.69, 0.00, null,
+ -0.69, 971.30);
+ checkInst(model, 189, 189, LocalDate.of(2019, 7, 9), 13, 187, 50.00, 50.00, null, 0.81907897, 40.95, 546.49, 0.64, 0.00, null,
+ -0.64, 971.30);
+ checkInst(model, 190, 190, LocalDate.of(2019, 7, 10), 12, 188, 50.00, 50.00, null, 0.81820528, 40.91, 497.08, 0.58, 0.00, null,
+ -0.58, 971.30);
+ checkInst(model, 191, 191, LocalDate.of(2019, 7, 11), 11, 189, 50.00, 50.00, null, 0.81733252, 40.87, 447.61, 0.53, 0.00, null,
+ -0.53, 971.30);
+ checkInst(model, 192, 192, LocalDate.of(2019, 7, 12), 10, 190, 50.00, 50.00, null, 0.81646069, 40.82, 398.08, 0.48, 0.00, null,
+ -0.48, 971.30);
+ checkInst(model, 193, 193, LocalDate.of(2019, 7, 13), 9, 191, 50.00, 50.00, null, 0.81558979, 40.78, 348.51, 0.43, 0.00, null,
+ -0.43, 971.30);
+ checkInst(model, 194, 194, LocalDate.of(2019, 7, 14), 8, 192, 50.00, 50.00, null, 0.81471983, 40.74, 298.88, 0.37, 0.00, null,
+ -0.37, 971.30);
+ checkInst(model, 195, 195, LocalDate.of(2019, 7, 15), 7, 193, 50.00, 50.00, null, 0.81385078, 40.69, 249.20, 0.32, 0.00, null,
+ -0.32, 971.30);
+ checkInst(model, 196, 196, LocalDate.of(2019, 7, 16), 6, 194, 50.00, 50.00, null, 0.81298267, 40.65, 199.47, 0.27, 0.00, null,
+ -0.27, 971.30);
+ checkInst(model, 197, 197, LocalDate.of(2019, 7, 17), 5, 195, 50.00, 50.00, null, 0.81211548, 40.61, 149.68, 0.21, 0.00, null,
+ -0.21, 971.30);
+ checkInst(model, 198, 198, LocalDate.of(2019, 7, 18), 4, 196, 50.00, 50.00, null, 0.81124922, 40.56, 99.84, 0.16, 0.00, null, -0.16,
+ 971.30);
+ checkInst(model, 199, 199, LocalDate.of(2019, 7, 19), 3, 197, 50.00, 50.00, null, 0.81038388, 40.52, 49.95, 0.11, 0.00, null, -0.11,
+ 971.30);
+
+ assertEquals(200, model.payments().size(), "disbursement + 199 regular (period 200 removed, forecast was 0)");
+ }
+
+ @Test
+ void testLessPayment_term200_originationFee1000_netDisbursement9000_pay40() {
+ final ProjectedAmortizationScheduleModel model = generateModel();
+ calculator.applyPayment(model, EXPECTED_DISBURSEMENT_DATE.plusDays(1), new BigDecimal("40"));
+
+ checkInst(model, 0, 0, EXPECTED_DISBURSEMENT_DATE, 201, 0, -9000.00, null, null, 1.00000000, -9000.00, 9000.00, null, null, null,
+ null, 1000.00);
+
+ checkInst(model, 1, 1, LocalDate.of(2019, 1, 2), 200, 0, 50.00, 50.00, 40.00, 1.00000000, 40.00, 8959.61, 9.61, 7.69, 7.69, -1.92,
+ 992.31);
+
+ checkInst(model, 2, 2, LocalDate.of(2019, 1, 3), 199, 1, 50.00, 50.00, null, 0.99893332, 49.95, 8919.18, 9.57, 0.00, null, -9.57,
+ 992.31);
+ checkInst(model, 3, 3, LocalDate.of(2019, 1, 4), 198, 2, 50.00, 50.00, null, 0.99786779, 49.89, 8878.70, 9.52, 0.00, null, -9.52,
+ 992.31);
+ checkInst(model, 4, 4, LocalDate.of(2019, 1, 5), 197, 3, 50.00, 50.00, null, 0.99680339, 49.84, 8838.18, 9.48, 0.00, null, -9.48,
+ 992.31);
+ checkInst(model, 5, 5, LocalDate.of(2019, 1, 6), 196, 4, 50.00, 50.00, null, 0.99574012, 49.79, 8797.62, 9.44, 0.00, null, -9.44,
+ 992.31);
+ checkInst(model, 6, 6, LocalDate.of(2019, 1, 7), 195, 5, 50.00, 50.00, null, 0.99467799, 49.73, 8757.01, 9.39, 0.00, null, -9.39,
+ 992.31);
+ checkInst(model, 7, 7, LocalDate.of(2019, 1, 8), 194, 6, 50.00, 50.00, null, 0.99361699, 49.68, 8716.36, 9.35, 0.00, null, -9.35,
+ 992.31);
+ checkInst(model, 8, 8, LocalDate.of(2019, 1, 9), 193, 7, 50.00, 50.00, null, 0.99255712, 49.63, 8675.67, 9.31, 0.00, null, -9.31,
+ 992.31);
+ checkInst(model, 9, 9, LocalDate.of(2019, 1, 10), 192, 8, 50.00, 50.00, null, 0.99149839, 49.57, 8634.94, 9.26, 0.00, null, -9.26,
+ 992.31);
+ checkInst(model, 10, 10, LocalDate.of(2019, 1, 11), 191, 9, 50.00, 50.00, null, 0.99044078, 49.52, 8594.16, 9.22, 0.00, null, -9.22,
+ 992.31);
+ checkInst(model, 11, 11, LocalDate.of(2019, 1, 12), 190, 10, 50.00, 50.00, null, 0.98938430, 49.47, 8553.33, 9.18, 0.00, null,
+ -9.18, 992.31);
+ checkInst(model, 12, 12, LocalDate.of(2019, 1, 13), 189, 11, 50.00, 50.00, null, 0.98832895, 49.42, 8512.47, 9.13, 0.00, null,
+ -9.13, 992.31);
+ checkInst(model, 13, 13, LocalDate.of(2019, 1, 14), 188, 12, 50.00, 50.00, null, 0.98727472, 49.36, 8471.56, 9.09, 0.00, null,
+ -9.09, 992.31);
+ checkInst(model, 14, 14, LocalDate.of(2019, 1, 15), 187, 13, 50.00, 50.00, null, 0.98622162, 49.31, 8430.60, 9.05, 0.00, null,
+ -9.05, 992.31);
+ checkInst(model, 15, 15, LocalDate.of(2019, 1, 16), 186, 14, 50.00, 50.00, null, 0.98516964, 49.26, 8389.61, 9.00, 0.00, null,
+ -9.00, 992.31);
+ checkInst(model, 16, 16, LocalDate.of(2019, 1, 17), 185, 15, 50.00, 50.00, null, 0.98411879, 49.21, 8348.56, 8.96, 0.00, null,
+ -8.96, 992.31);
+ checkInst(model, 17, 17, LocalDate.of(2019, 1, 18), 184, 16, 50.00, 50.00, null, 0.98306905, 49.15, 8307.48, 8.91, 0.00, null,
+ -8.91, 992.31);
+ checkInst(model, 18, 18, LocalDate.of(2019, 1, 19), 183, 17, 50.00, 50.00, null, 0.98202044, 49.10, 8266.35, 8.87, 0.00, null,
+ -8.87, 992.31);
+ checkInst(model, 19, 19, LocalDate.of(2019, 1, 20), 182, 18, 50.00, 50.00, null, 0.98097294, 49.05, 8225.18, 8.83, 0.00, null,
+ -8.83, 992.31);
+ checkInst(model, 20, 20, LocalDate.of(2019, 1, 21), 181, 19, 50.00, 50.00, null, 0.97992656, 49.00, 8183.96, 8.78, 0.00, null,
+ -8.78, 992.31);
+ checkInst(model, 21, 21, LocalDate.of(2019, 1, 22), 180, 20, 50.00, 50.00, null, 0.97888129, 48.94, 8142.70, 8.74, 0.00, null,
+ -8.74, 992.31);
+ checkInst(model, 22, 22, LocalDate.of(2019, 1, 23), 179, 21, 50.00, 50.00, null, 0.97783715, 48.89, 8101.39, 8.69, 0.00, null,
+ -8.69, 992.31);
+ checkInst(model, 23, 23, LocalDate.of(2019, 1, 24), 178, 22, 50.00, 50.00, null, 0.97679411, 48.84, 8060.04, 8.65, 0.00, null,
+ -8.65, 992.31);
+ checkInst(model, 24, 24, LocalDate.of(2019, 1, 25), 177, 23, 50.00, 50.00, null, 0.97575219, 48.79, 8018.65, 8.61, 0.00, null,
+ -8.61, 992.31);
+ checkInst(model, 25, 25, LocalDate.of(2019, 1, 26), 176, 24, 50.00, 50.00, null, 0.97471138, 48.74, 7977.21, 8.56, 0.00, null,
+ -8.56, 992.31);
+ checkInst(model, 26, 26, LocalDate.of(2019, 1, 27), 175, 25, 50.00, 50.00, null, 0.97367168, 48.68, 7935.73, 8.52, 0.00, null,
+ -8.52, 992.31);
+ checkInst(model, 27, 27, LocalDate.of(2019, 1, 28), 174, 26, 50.00, 50.00, null, 0.97263309, 48.63, 7894.21, 8.47, 0.00, null,
+ -8.47, 992.31);
+ checkInst(model, 28, 28, LocalDate.of(2019, 1, 29), 173, 27, 50.00, 50.00, null, 0.97159560, 48.58, 7852.63, 8.43, 0.00, null,
+ -8.43, 992.31);
+ checkInst(model, 29, 29, LocalDate.of(2019, 1, 30), 172, 28, 50.00, 50.00, null, 0.97055922, 48.53, 7811.02, 8.39, 0.00, null,
+ -8.39, 992.31);
+ checkInst(model, 30, 30, LocalDate.of(2019, 1, 31), 171, 29, 50.00, 50.00, null, 0.96952395, 48.48, 7769.36, 8.34, 0.00, null,
+ -8.34, 992.31);
+ checkInst(model, 31, 31, LocalDate.of(2019, 2, 1), 170, 30, 50.00, 50.00, null, 0.96848979, 48.42, 7727.66, 8.30, 0.00, null, -8.30,
+ 992.31);
+ checkInst(model, 32, 32, LocalDate.of(2019, 2, 2), 169, 31, 50.00, 50.00, null, 0.96745672, 48.37, 7685.91, 8.25, 0.00, null, -8.25,
+ 992.31);
+ checkInst(model, 33, 33, LocalDate.of(2019, 2, 3), 168, 32, 50.00, 50.00, null, 0.96642476, 48.32, 7644.12, 8.21, 0.00, null, -8.21,
+ 992.31);
+ checkInst(model, 34, 34, LocalDate.of(2019, 2, 4), 167, 33, 50.00, 50.00, null, 0.96539390, 48.27, 7602.28, 8.16, 0.00, null, -8.16,
+ 992.31);
+ checkInst(model, 35, 35, LocalDate.of(2019, 2, 5), 166, 34, 50.00, 50.00, null, 0.96436413, 48.22, 7560.40, 8.12, 0.00, null, -8.12,
+ 992.31);
+ checkInst(model, 36, 36, LocalDate.of(2019, 2, 6), 165, 35, 50.00, 50.00, null, 0.96333547, 48.17, 7518.47, 8.07, 0.00, null, -8.07,
+ 992.31);
+ checkInst(model, 37, 37, LocalDate.of(2019, 2, 7), 164, 36, 50.00, 50.00, null, 0.96230790, 48.12, 7476.50, 8.03, 0.00, null, -8.03,
+ 992.31);
+ checkInst(model, 38, 38, LocalDate.of(2019, 2, 8), 163, 37, 50.00, 50.00, null, 0.96128143, 48.06, 7434.48, 7.98, 0.00, null, -7.98,
+ 992.31);
+ checkInst(model, 39, 39, LocalDate.of(2019, 2, 9), 162, 38, 50.00, 50.00, null, 0.96025606, 48.01, 7392.42, 7.94, 0.00, null, -7.94,
+ 992.31);
+ checkInst(model, 40, 40, LocalDate.of(2019, 2, 10), 161, 39, 50.00, 50.00, null, 0.95923178, 47.96, 7350.31, 7.89, 0.00, null,
+ -7.89, 992.31);
+ checkInst(model, 41, 41, LocalDate.of(2019, 2, 11), 160, 40, 50.00, 50.00, null, 0.95820859, 47.91, 7308.16, 7.85, 0.00, null,
+ -7.85, 992.31);
+ checkInst(model, 42, 42, LocalDate.of(2019, 2, 12), 159, 41, 50.00, 50.00, null, 0.95718649, 47.86, 7265.97, 7.80, 0.00, null,
+ -7.80, 992.31);
+ checkInst(model, 43, 43, LocalDate.of(2019, 2, 13), 158, 42, 50.00, 50.00, null, 0.95616548, 47.81, 7223.72, 7.76, 0.00, null,
+ -7.76, 992.31);
+ checkInst(model, 44, 44, LocalDate.of(2019, 2, 14), 157, 43, 50.00, 50.00, null, 0.95514557, 47.76, 7181.44, 7.71, 0.00, null,
+ -7.71, 992.31);
+ checkInst(model, 45, 45, LocalDate.of(2019, 2, 15), 156, 44, 50.00, 50.00, null, 0.95412674, 47.71, 7139.11, 7.67, 0.00, null,
+ -7.67, 992.31);
+ checkInst(model, 46, 46, LocalDate.of(2019, 2, 16), 155, 45, 50.00, 50.00, null, 0.95310899, 47.66, 7096.73, 7.62, 0.00, null,
+ -7.62, 992.31);
+ checkInst(model, 47, 47, LocalDate.of(2019, 2, 17), 154, 46, 50.00, 50.00, null, 0.95209233, 47.60, 7054.31, 7.58, 0.00, null,
+ -7.58, 992.31);
+ checkInst(model, 48, 48, LocalDate.of(2019, 2, 18), 153, 47, 50.00, 50.00, null, 0.95107676, 47.55, 7011.84, 7.53, 0.00, null,
+ -7.53, 992.31);
+ checkInst(model, 49, 49, LocalDate.of(2019, 2, 19), 152, 48, 50.00, 50.00, null, 0.95006227, 47.50, 6969.33, 7.49, 0.00, null,
+ -7.49, 992.31);
+ checkInst(model, 50, 50, LocalDate.of(2019, 2, 20), 151, 49, 50.00, 50.00, null, 0.94904886, 47.45, 6926.77, 7.44, 0.00, null,
+ -7.44, 992.31);
+ checkInst(model, 51, 51, LocalDate.of(2019, 2, 21), 150, 50, 50.00, 50.00, null, 0.94803653, 47.40, 6884.17, 7.40, 0.00, null,
+ -7.40, 992.31);
+ checkInst(model, 52, 52, LocalDate.of(2019, 2, 22), 149, 51, 50.00, 50.00, null, 0.94702529, 47.35, 6841.52, 7.35, 0.00, null,
+ -7.35, 992.31);
+ checkInst(model, 53, 53, LocalDate.of(2019, 2, 23), 148, 52, 50.00, 50.00, null, 0.94601512, 47.30, 6798.82, 7.31, 0.00, null,
+ -7.31, 992.31);
+ checkInst(model, 54, 54, LocalDate.of(2019, 2, 24), 147, 53, 50.00, 50.00, null, 0.94500603, 47.25, 6756.08, 7.26, 0.00, null,
+ -7.26, 992.31);
+ checkInst(model, 55, 55, LocalDate.of(2019, 2, 25), 146, 54, 50.00, 50.00, null, 0.94399801, 47.20, 6713.30, 7.21, 0.00, null,
+ -7.21, 992.31);
+ checkInst(model, 56, 56, LocalDate.of(2019, 2, 26), 145, 55, 50.00, 50.00, null, 0.94299107, 47.15, 6670.47, 7.17, 0.00, null,
+ -7.17, 992.31);
+ checkInst(model, 57, 57, LocalDate.of(2019, 2, 27), 144, 56, 50.00, 50.00, null, 0.94198521, 47.10, 6627.59, 7.12, 0.00, null,
+ -7.12, 992.31);
+ checkInst(model, 58, 58, LocalDate.of(2019, 2, 28), 143, 57, 50.00, 50.00, null, 0.94098042, 47.05, 6584.67, 7.08, 0.00, null,
+ -7.08, 992.31);
+ checkInst(model, 59, 59, LocalDate.of(2019, 3, 1), 142, 58, 50.00, 50.00, null, 0.93997669, 47.00, 6541.70, 7.03, 0.00, null, -7.03,
+ 992.31);
+ checkInst(model, 60, 60, LocalDate.of(2019, 3, 2), 141, 59, 50.00, 50.00, null, 0.93897404, 46.95, 6498.68, 6.99, 0.00, null, -6.99,
+ 992.31);
+ checkInst(model, 61, 61, LocalDate.of(2019, 3, 3), 140, 60, 50.00, 50.00, null, 0.93797246, 46.90, 6455.62, 6.94, 0.00, null, -6.94,
+ 992.31);
+ checkInst(model, 62, 62, LocalDate.of(2019, 3, 4), 139, 61, 50.00, 50.00, null, 0.93697195, 46.85, 6412.51, 6.89, 0.00, null, -6.89,
+ 992.31);
+ checkInst(model, 63, 63, LocalDate.of(2019, 3, 5), 138, 62, 50.00, 50.00, null, 0.93597251, 46.80, 6369.36, 6.85, 0.00, null, -6.85,
+ 992.31);
+ checkInst(model, 64, 64, LocalDate.of(2019, 3, 6), 137, 63, 50.00, 50.00, null, 0.93497413, 46.75, 6326.16, 6.80, 0.00, null, -6.80,
+ 992.31);
+ checkInst(model, 65, 65, LocalDate.of(2019, 3, 7), 136, 64, 50.00, 50.00, null, 0.93397681, 46.70, 6282.92, 6.76, 0.00, null, -6.76,
+ 992.31);
+ checkInst(model, 66, 66, LocalDate.of(2019, 3, 8), 135, 65, 50.00, 50.00, null, 0.93298056, 46.65, 6239.63, 6.71, 0.00, null, -6.71,
+ 992.31);
+ checkInst(model, 67, 67, LocalDate.of(2019, 3, 9), 134, 66, 50.00, 50.00, null, 0.93198538, 46.60, 6196.29, 6.66, 0.00, null, -6.66,
+ 992.31);
+ checkInst(model, 68, 68, LocalDate.of(2019, 3, 10), 133, 67, 50.00, 50.00, null, 0.93099125, 46.55, 6152.91, 6.62, 0.00, null,
+ -6.62, 992.31);
+ checkInst(model, 69, 69, LocalDate.of(2019, 3, 11), 132, 68, 50.00, 50.00, null, 0.92999818, 46.50, 6109.48, 6.57, 0.00, null,
+ -6.57, 992.31);
+ checkInst(model, 70, 70, LocalDate.of(2019, 3, 12), 131, 69, 50.00, 50.00, null, 0.92900618, 46.45, 6066.00, 6.52, 0.00, null,
+ -6.52, 992.31);
+ checkInst(model, 71, 71, LocalDate.of(2019, 3, 13), 130, 70, 50.00, 50.00, null, 0.92801523, 46.40, 6022.48, 6.48, 0.00, null,
+ -6.48, 992.31);
+ checkInst(model, 72, 72, LocalDate.of(2019, 3, 14), 129, 71, 50.00, 50.00, null, 0.92702534, 46.35, 5978.91, 6.43, 0.00, null,
+ -6.43, 992.31);
+ checkInst(model, 73, 73, LocalDate.of(2019, 3, 15), 128, 72, 50.00, 50.00, null, 0.92603650, 46.30, 5935.29, 6.38, 0.00, null,
+ -6.38, 992.31);
+ checkInst(model, 74, 74, LocalDate.of(2019, 3, 16), 127, 73, 50.00, 50.00, null, 0.92504872, 46.25, 5891.63, 6.34, 0.00, null,
+ -6.34, 992.31);
+ checkInst(model, 75, 75, LocalDate.of(2019, 3, 17), 126, 74, 50.00, 50.00, null, 0.92406200, 46.20, 5847.92, 6.29, 0.00, null,
+ -6.29, 992.31);
+ checkInst(model, 76, 76, LocalDate.of(2019, 3, 18), 125, 75, 50.00, 50.00, null, 0.92307632, 46.15, 5804.17, 6.24, 0.00, null,
+ -6.24, 992.31);
+ checkInst(model, 77, 77, LocalDate.of(2019, 3, 19), 124, 76, 50.00, 50.00, null, 0.92209170, 46.10, 5760.36, 6.20, 0.00, null,
+ -6.20, 992.31);
+ checkInst(model, 78, 78, LocalDate.of(2019, 3, 20), 123, 77, 50.00, 50.00, null, 0.92110813, 46.06, 5716.52, 6.15, 0.00, null,
+ -6.15, 992.31);
+ checkInst(model, 79, 79, LocalDate.of(2019, 3, 21), 122, 78, 50.00, 50.00, null, 0.92012560, 46.01, 5672.62, 6.10, 0.00, null,
+ -6.10, 992.31);
+ checkInst(model, 80, 80, LocalDate.of(2019, 3, 22), 121, 79, 50.00, 50.00, null, 0.91914413, 45.96, 5628.68, 6.06, 0.00, null,
+ -6.06, 992.31);
+ checkInst(model, 81, 81, LocalDate.of(2019, 3, 23), 120, 80, 50.00, 50.00, null, 0.91816370, 45.91, 5584.69, 6.01, 0.00, null,
+ -6.01, 992.31);
+ checkInst(model, 82, 82, LocalDate.of(2019, 3, 24), 119, 81, 50.00, 50.00, null, 0.91718432, 45.86, 5540.65, 5.96, 0.00, null,
+ -5.96, 992.31);
+ checkInst(model, 83, 83, LocalDate.of(2019, 3, 25), 118, 82, 50.00, 50.00, null, 0.91620598, 45.81, 5496.57, 5.92, 0.00, null,
+ -5.92, 992.31);
+ checkInst(model, 84, 84, LocalDate.of(2019, 3, 26), 117, 83, 50.00, 50.00, null, 0.91522868, 45.76, 5452.44, 5.87, 0.00, null,
+ -5.87, 992.31);
+ checkInst(model, 85, 85, LocalDate.of(2019, 3, 27), 116, 84, 50.00, 50.00, null, 0.91425243, 45.71, 5408.26, 5.82, 0.00, null,
+ -5.82, 992.31);
+ checkInst(model, 86, 86, LocalDate.of(2019, 3, 28), 115, 85, 50.00, 50.00, null, 0.91327722, 45.66, 5364.03, 5.78, 0.00, null,
+ -5.78, 992.31);
+ checkInst(model, 87, 87, LocalDate.of(2019, 3, 29), 114, 86, 50.00, 50.00, null, 0.91230305, 45.62, 5319.76, 5.73, 0.00, null,
+ -5.73, 992.31);
+ checkInst(model, 88, 88, LocalDate.of(2019, 3, 30), 113, 87, 50.00, 50.00, null, 0.91132992, 45.57, 5275.44, 5.68, 0.00, null,
+ -5.68, 992.31);
+ checkInst(model, 89, 89, LocalDate.of(2019, 3, 31), 112, 88, 50.00, 50.00, null, 0.91035783, 45.52, 5231.08, 5.63, 0.00, null,
+ -5.63, 992.31);
+ checkInst(model, 90, 90, LocalDate.of(2019, 4, 1), 111, 89, 50.00, 50.00, null, 0.90938677, 45.47, 5186.66, 5.59, 0.00, null, -5.59,
+ 992.31);
+ checkInst(model, 91, 91, LocalDate.of(2019, 4, 2), 110, 90, 50.00, 50.00, null, 0.90841675, 45.42, 5142.20, 5.54, 0.00, null, -5.54,
+ 992.31);
+ checkInst(model, 92, 92, LocalDate.of(2019, 4, 3), 109, 91, 50.00, 50.00, null, 0.90744776, 45.37, 5097.69, 5.49, 0.00, null, -5.49,
+ 992.31);
+ checkInst(model, 93, 93, LocalDate.of(2019, 4, 4), 108, 92, 50.00, 50.00, null, 0.90647981, 45.32, 5053.13, 5.44, 0.00, null, -5.44,
+ 992.31);
+ checkInst(model, 94, 94, LocalDate.of(2019, 4, 5), 107, 93, 50.00, 50.00, null, 0.90551289, 45.28, 5008.53, 5.40, 0.00, null, -5.40,
+ 992.31);
+ checkInst(model, 95, 95, LocalDate.of(2019, 4, 6), 106, 94, 50.00, 50.00, null, 0.90454700, 45.23, 4963.88, 5.35, 0.00, null, -5.35,
+ 992.31);
+ checkInst(model, 96, 96, LocalDate.of(2019, 4, 7), 105, 95, 50.00, 50.00, null, 0.90358215, 45.18, 4919.18, 5.30, 0.00, null, -5.30,
+ 992.31);
+ checkInst(model, 97, 97, LocalDate.of(2019, 4, 8), 104, 96, 50.00, 50.00, null, 0.90261832, 45.13, 4874.43, 5.25, 0.00, null, -5.25,
+ 992.31);
+ checkInst(model, 98, 98, LocalDate.of(2019, 4, 9), 103, 97, 50.00, 50.00, null, 0.90165552, 45.08, 4829.64, 5.20, 0.00, null, -5.20,
+ 992.31);
+ checkInst(model, 99, 99, LocalDate.of(2019, 4, 10), 102, 98, 50.00, 50.00, null, 0.90069374, 45.03, 4784.79, 5.16, 0.00, null,
+ -5.16, 992.31);
+ checkInst(model, 100, 100, LocalDate.of(2019, 4, 11), 101, 99, 50.00, 50.00, null, 0.89973299, 44.99, 4739.90, 5.11, 0.00, null,
+ -5.11, 992.31);
+ checkInst(model, 101, 101, LocalDate.of(2019, 4, 12), 100, 100, 50.00, 50.00, null, 0.89877327, 44.94, 4694.96, 5.06, 0.00, null,
+ -5.06, 992.31);
+ checkInst(model, 102, 102, LocalDate.of(2019, 4, 13), 99, 101, 50.00, 50.00, null, 0.89781457, 44.89, 4649.98, 5.01, 0.00, null,
+ -5.01, 992.31);
+ checkInst(model, 103, 103, LocalDate.of(2019, 4, 14), 98, 102, 50.00, 50.00, null, 0.89685689, 44.84, 4604.94, 4.97, 0.00, null,
+ -4.97, 992.31);
+ checkInst(model, 104, 104, LocalDate.of(2019, 4, 15), 97, 103, 50.00, 50.00, null, 0.89590024, 44.80, 4559.86, 4.92, 0.00, null,
+ -4.92, 992.31);
+ checkInst(model, 105, 105, LocalDate.of(2019, 4, 16), 96, 104, 50.00, 50.00, null, 0.89494460, 44.75, 4514.73, 4.87, 0.00, null,
+ -4.87, 992.31);
+ checkInst(model, 106, 106, LocalDate.of(2019, 4, 17), 95, 105, 50.00, 50.00, null, 0.89398999, 44.70, 4469.55, 4.82, 0.00, null,
+ -4.82, 992.31);
+ checkInst(model, 107, 107, LocalDate.of(2019, 4, 18), 94, 106, 50.00, 50.00, null, 0.89303639, 44.65, 4424.32, 4.77, 0.00, null,
+ -4.77, 992.31);
+ checkInst(model, 108, 108, LocalDate.of(2019, 4, 19), 93, 107, 50.00, 50.00, null, 0.89208381, 44.60, 4379.05, 4.72, 0.00, null,
+ -4.72, 992.31);
+ checkInst(model, 109, 109, LocalDate.of(2019, 4, 20), 92, 108, 50.00, 50.00, null, 0.89113225, 44.56, 4333.72, 4.68, 0.00, null,
+ -4.68, 992.31);
+ checkInst(model, 110, 110, LocalDate.of(2019, 4, 21), 91, 109, 50.00, 50.00, null, 0.89018170, 44.51, 4288.35, 4.63, 0.00, null,
+ -4.63, 992.31);
+ checkInst(model, 111, 111, LocalDate.of(2019, 4, 22), 90, 110, 50.00, 50.00, null, 0.88923216, 44.46, 4242.93, 4.58, 0.00, null,
+ -4.58, 992.31);
+ checkInst(model, 112, 112, LocalDate.of(2019, 4, 23), 89, 111, 50.00, 50.00, null, 0.88828364, 44.41, 4197.46, 4.53, 0.00, null,
+ -4.53, 992.31);
+ checkInst(model, 113, 113, LocalDate.of(2019, 4, 24), 88, 112, 50.00, 50.00, null, 0.88733613, 44.37, 4151.94, 4.48, 0.00, null,
+ -4.48, 992.31);
+ checkInst(model, 114, 114, LocalDate.of(2019, 4, 25), 87, 113, 50.00, 50.00, null, 0.88638963, 44.32, 4106.38, 4.43, 0.00, null,
+ -4.43, 992.31);
+ checkInst(model, 115, 115, LocalDate.of(2019, 4, 26), 86, 114, 50.00, 50.00, null, 0.88544414, 44.27, 4060.76, 4.38, 0.00, null,
+ -4.38, 992.31);
+ checkInst(model, 116, 116, LocalDate.of(2019, 4, 27), 85, 115, 50.00, 50.00, null, 0.88449966, 44.22, 4015.10, 4.34, 0.00, null,
+ -4.34, 992.31);
+ checkInst(model, 117, 117, LocalDate.of(2019, 4, 28), 84, 116, 50.00, 50.00, null, 0.88355619, 44.18, 3969.38, 4.29, 0.00, null,
+ -4.29, 992.31);
+ checkInst(model, 118, 118, LocalDate.of(2019, 4, 29), 83, 117, 50.00, 50.00, null, 0.88261372, 44.13, 3923.62, 4.24, 0.00, null,
+ -4.24, 992.31);
+ checkInst(model, 119, 119, LocalDate.of(2019, 4, 30), 82, 118, 50.00, 50.00, null, 0.88167226, 44.08, 3877.81, 4.19, 0.00, null,
+ -4.19, 992.31);
+ checkInst(model, 120, 120, LocalDate.of(2019, 5, 1), 81, 119, 50.00, 50.00, null, 0.88073180, 44.04, 3831.95, 4.14, 0.00, null,
+ -4.14, 992.31);
+ checkInst(model, 121, 121, LocalDate.of(2019, 5, 2), 80, 120, 50.00, 50.00, null, 0.87979234, 43.99, 3786.04, 4.09, 0.00, null,
+ -4.09, 992.31);
+ checkInst(model, 122, 122, LocalDate.of(2019, 5, 3), 79, 121, 50.00, 50.00, null, 0.87885389, 43.94, 3740.09, 4.04, 0.00, null,
+ -4.04, 992.31);
+ checkInst(model, 123, 123, LocalDate.of(2019, 5, 4), 78, 122, 50.00, 50.00, null, 0.87791644, 43.90, 3694.08, 3.99, 0.00, null,
+ -3.99, 992.31);
+ checkInst(model, 124, 124, LocalDate.of(2019, 5, 5), 77, 123, 50.00, 50.00, null, 0.87697999, 43.85, 3648.03, 3.94, 0.00, null,
+ -3.94, 992.31);
+ checkInst(model, 125, 125, LocalDate.of(2019, 5, 6), 76, 124, 50.00, 50.00, null, 0.87604453, 43.80, 3601.92, 3.90, 0.00, null,
+ -3.90, 992.31);
+ checkInst(model, 126, 126, LocalDate.of(2019, 5, 7), 75, 125, 50.00, 50.00, null, 0.87511008, 43.76, 3555.77, 3.85, 0.00, null,
+ -3.85, 992.31);
+ checkInst(model, 127, 127, LocalDate.of(2019, 5, 8), 74, 126, 50.00, 50.00, null, 0.87417662, 43.71, 3509.56, 3.80, 0.00, null,
+ -3.80, 992.31);
+ checkInst(model, 128, 128, LocalDate.of(2019, 5, 9), 73, 127, 50.00, 50.00, null, 0.87324416, 43.66, 3463.31, 3.75, 0.00, null,
+ -3.75, 992.31);
+ checkInst(model, 129, 129, LocalDate.of(2019, 5, 10), 72, 128, 50.00, 50.00, null, 0.87231269, 43.62, 3417.01, 3.70, 0.00, null,
+ -3.70, 992.31);
+ checkInst(model, 130, 130, LocalDate.of(2019, 5, 11), 71, 129, 50.00, 50.00, null, 0.87138221, 43.57, 3370.66, 3.65, 0.00, null,
+ -3.65, 992.31);
+ checkInst(model, 131, 131, LocalDate.of(2019, 5, 12), 70, 130, 50.00, 50.00, null, 0.87045273, 43.52, 3324.26, 3.60, 0.00, null,
+ -3.60, 992.31);
+ checkInst(model, 132, 132, LocalDate.of(2019, 5, 13), 69, 131, 50.00, 50.00, null, 0.86952424, 43.48, 3277.81, 3.55, 0.00, null,
+ -3.55, 992.31);
+ checkInst(model, 133, 133, LocalDate.of(2019, 5, 14), 68, 132, 50.00, 50.00, null, 0.86859674, 43.43, 3231.31, 3.50, 0.00, null,
+ -3.50, 992.31);
+ checkInst(model, 134, 134, LocalDate.of(2019, 5, 15), 67, 133, 50.00, 50.00, null, 0.86767023, 43.38, 3184.76, 3.45, 0.00, null,
+ -3.45, 992.31);
+ checkInst(model, 135, 135, LocalDate.of(2019, 5, 16), 66, 134, 50.00, 50.00, null, 0.86674471, 43.34, 3138.16, 3.40, 0.00, null,
+ -3.40, 992.31);
+ checkInst(model, 136, 136, LocalDate.of(2019, 5, 17), 65, 135, 50.00, 50.00, null, 0.86582017, 43.29, 3091.51, 3.35, 0.00, null,
+ -3.35, 992.31);
+ checkInst(model, 137, 137, LocalDate.of(2019, 5, 18), 64, 136, 50.00, 50.00, null, 0.86489662, 43.24, 3044.81, 3.30, 0.00, null,
+ -3.30, 992.31);
+ checkInst(model, 138, 138, LocalDate.of(2019, 5, 19), 63, 137, 50.00, 50.00, null, 0.86397406, 43.20, 2998.06, 3.25, 0.00, null,
+ -3.25, 992.31);
+ checkInst(model, 139, 139, LocalDate.of(2019, 5, 20), 62, 138, 50.00, 50.00, null, 0.86305248, 43.15, 2951.26, 3.20, 0.00, null,
+ -3.20, 992.31);
+ checkInst(model, 140, 140, LocalDate.of(2019, 5, 21), 61, 139, 50.00, 50.00, null, 0.86213188, 43.11, 2904.42, 3.15, 0.00, null,
+ -3.15, 992.31);
+ checkInst(model, 141, 141, LocalDate.of(2019, 5, 22), 60, 140, 50.00, 50.00, null, 0.86121227, 43.06, 2857.52, 3.10, 0.00, null,
+ -3.10, 992.31);
+ checkInst(model, 142, 142, LocalDate.of(2019, 5, 23), 59, 141, 50.00, 50.00, null, 0.86029363, 43.01, 2810.57, 3.05, 0.00, null,
+ -3.05, 992.31);
+ checkInst(model, 143, 143, LocalDate.of(2019, 5, 24), 58, 142, 50.00, 50.00, null, 0.85937598, 42.97, 2763.57, 3.00, 0.00, null,
+ -3.00, 992.31);
+ checkInst(model, 144, 144, LocalDate.of(2019, 5, 25), 57, 143, 50.00, 50.00, null, 0.85845930, 42.92, 2716.52, 2.95, 0.00, null,
+ -2.95, 992.31);
+ checkInst(model, 145, 145, LocalDate.of(2019, 5, 26), 56, 144, 50.00, 50.00, null, 0.85754361, 42.88, 2669.42, 2.90, 0.00, null,
+ -2.90, 992.31);
+ checkInst(model, 146, 146, LocalDate.of(2019, 5, 27), 55, 145, 50.00, 50.00, null, 0.85662889, 42.83, 2622.27, 2.85, 0.00, null,
+ -2.85, 992.31);
+ checkInst(model, 147, 147, LocalDate.of(2019, 5, 28), 54, 146, 50.00, 50.00, null, 0.85571514, 42.79, 2575.07, 2.80, 0.00, null,
+ -2.80, 992.31);
+ checkInst(model, 148, 148, LocalDate.of(2019, 5, 29), 53, 147, 50.00, 50.00, null, 0.85480237, 42.74, 2527.82, 2.75, 0.00, null,
+ -2.75, 992.31);
+ checkInst(model, 149, 149, LocalDate.of(2019, 5, 30), 52, 148, 50.00, 50.00, null, 0.85389057, 42.69, 2480.52, 2.70, 0.00, null,
+ -2.70, 992.31);
+ checkInst(model, 150, 150, LocalDate.of(2019, 5, 31), 51, 149, 50.00, 50.00, null, 0.85297975, 42.65, 2433.17, 2.65, 0.00, null,
+ -2.65, 992.31);
+ checkInst(model, 151, 151, LocalDate.of(2019, 6, 1), 50, 150, 50.00, 50.00, null, 0.85206990, 42.60, 2385.77, 2.60, 0.00, null,
+ -2.60, 992.31);
+ checkInst(model, 152, 152, LocalDate.of(2019, 6, 2), 49, 151, 50.00, 50.00, null, 0.85116101, 42.56, 2338.31, 2.55, 0.00, null,
+ -2.55, 992.31);
+ checkInst(model, 153, 153, LocalDate.of(2019, 6, 3), 48, 152, 50.00, 50.00, null, 0.85025310, 42.51, 2290.81, 2.50, 0.00, null,
+ -2.50, 992.31);
+ checkInst(model, 154, 154, LocalDate.of(2019, 6, 4), 47, 153, 50.00, 50.00, null, 0.84934616, 42.47, 2243.26, 2.45, 0.00, null,
+ -2.45, 992.31);
+ checkInst(model, 155, 155, LocalDate.of(2019, 6, 5), 46, 154, 50.00, 50.00, null, 0.84844018, 42.42, 2195.65, 2.40, 0.00, null,
+ -2.40, 992.31);
+ checkInst(model, 156, 156, LocalDate.of(2019, 6, 6), 45, 155, 50.00, 50.00, null, 0.84753517, 42.38, 2148.00, 2.34, 0.00, null,
+ -2.34, 992.31);
+ checkInst(model, 157, 157, LocalDate.of(2019, 6, 7), 44, 156, 50.00, 50.00, null, 0.84663113, 42.33, 2100.29, 2.29, 0.00, null,
+ -2.29, 992.31);
+ checkInst(model, 158, 158, LocalDate.of(2019, 6, 8), 43, 157, 50.00, 50.00, null, 0.84572805, 42.29, 2052.53, 2.24, 0.00, null,
+ -2.24, 992.31);
+ checkInst(model, 159, 159, LocalDate.of(2019, 6, 9), 42, 158, 50.00, 50.00, null, 0.84482593, 42.24, 2004.73, 2.19, 0.00, null,
+ -2.19, 992.31);
+ checkInst(model, 160, 160, LocalDate.of(2019, 6, 10), 41, 159, 50.00, 50.00, null, 0.84392477, 42.20, 1956.87, 2.14, 0.00, null,
+ -2.14, 992.31);
+ checkInst(model, 161, 161, LocalDate.of(2019, 6, 11), 40, 160, 50.00, 50.00, null, 0.84302458, 42.15, 1908.96, 2.09, 0.00, null,
+ -2.09, 992.31);
+ checkInst(model, 162, 162, LocalDate.of(2019, 6, 12), 39, 161, 50.00, 50.00, null, 0.84212535, 42.11, 1860.99, 2.04, 0.00, null,
+ -2.04, 992.31);
+ checkInst(model, 163, 163, LocalDate.of(2019, 6, 13), 38, 162, 50.00, 50.00, null, 0.84122707, 42.06, 1812.98, 1.99, 0.00, null,
+ -1.99, 992.31);
+ checkInst(model, 164, 164, LocalDate.of(2019, 6, 14), 37, 163, 50.00, 50.00, null, 0.84032975, 42.02, 1764.92, 1.94, 0.00, null,
+ -1.94, 992.31);
+ checkInst(model, 165, 165, LocalDate.of(2019, 6, 15), 36, 164, 50.00, 50.00, null, 0.83943340, 41.97, 1716.80, 1.88, 0.00, null,
+ -1.88, 992.31);
+ checkInst(model, 166, 166, LocalDate.of(2019, 6, 16), 35, 165, 50.00, 50.00, null, 0.83853799, 41.93, 1668.64, 1.83, 0.00, null,
+ -1.83, 992.31);
+ checkInst(model, 167, 167, LocalDate.of(2019, 6, 17), 34, 166, 50.00, 50.00, null, 0.83764354, 41.88, 1620.42, 1.78, 0.00, null,
+ -1.78, 992.31);
+ checkInst(model, 168, 168, LocalDate.of(2019, 6, 18), 33, 167, 50.00, 50.00, null, 0.83675005, 41.84, 1572.15, 1.73, 0.00, null,
+ -1.73, 992.31);
+ checkInst(model, 169, 169, LocalDate.of(2019, 6, 19), 32, 168, 50.00, 50.00, null, 0.83585751, 41.79, 1523.83, 1.68, 0.00, null,
+ -1.68, 992.31);
+ checkInst(model, 170, 170, LocalDate.of(2019, 6, 20), 31, 169, 50.00, 50.00, null, 0.83496592, 41.75, 1475.45, 1.63, 0.00, null,
+ -1.63, 992.31);
+ checkInst(model, 171, 171, LocalDate.of(2019, 6, 21), 30, 170, 50.00, 50.00, null, 0.83407528, 41.70, 1427.03, 1.58, 0.00, null,
+ -1.58, 992.31);
+ checkInst(model, 172, 172, LocalDate.of(2019, 6, 22), 29, 171, 50.00, 50.00, null, 0.83318560, 41.66, 1378.55, 1.52, 0.00, null,
+ -1.52, 992.31);
+ checkInst(model, 173, 173, LocalDate.of(2019, 6, 23), 28, 172, 50.00, 50.00, null, 0.83229686, 41.61, 1330.02, 1.47, 0.00, null,
+ -1.47, 992.31);
+ checkInst(model, 174, 174, LocalDate.of(2019, 6, 24), 27, 173, 50.00, 50.00, null, 0.83140907, 41.57, 1281.45, 1.42, 0.00, null,
+ -1.42, 992.31);
+ checkInst(model, 175, 175, LocalDate.of(2019, 6, 25), 26, 174, 50.00, 50.00, null, 0.83052222, 41.53, 1232.81, 1.37, 0.00, null,
+ -1.37, 992.31);
+ checkInst(model, 176, 176, LocalDate.of(2019, 6, 26), 25, 175, 50.00, 50.00, null, 0.82963633, 41.48, 1184.13, 1.32, 0.00, null,
+ -1.32, 992.31);
+ checkInst(model, 177, 177, LocalDate.of(2019, 6, 27), 24, 176, 50.00, 50.00, null, 0.82875137, 41.44, 1135.39, 1.26, 0.00, null,
+ -1.26, 992.31);
+ checkInst(model, 178, 178, LocalDate.of(2019, 6, 28), 23, 177, 50.00, 50.00, null, 0.82786736, 41.39, 1086.61, 1.21, 0.00, null,
+ -1.21, 992.31);
+ checkInst(model, 179, 179, LocalDate.of(2019, 6, 29), 22, 178, 50.00, 50.00, null, 0.82698430, 41.35, 1037.77, 1.16, 0.00, null,
+ -1.16, 992.31);
+ checkInst(model, 180, 180, LocalDate.of(2019, 6, 30), 21, 179, 50.00, 50.00, null, 0.82610217, 41.31, 988.88, 1.11, 0.00, null,
+ -1.11, 992.31);
+ checkInst(model, 181, 181, LocalDate.of(2019, 7, 1), 20, 180, 50.00, 50.00, null, 0.82522099, 41.26, 939.93, 1.06, 0.00, null,
+ -1.06, 992.31);
+ checkInst(model, 182, 182, LocalDate.of(2019, 7, 2), 19, 181, 50.00, 50.00, null, 0.82434075, 41.22, 890.93, 1.00, 0.00, null,
+ -1.00, 992.31);
+ checkInst(model, 183, 183, LocalDate.of(2019, 7, 3), 18, 182, 50.00, 50.00, null, 0.82346144, 41.17, 841.89, 0.95, 0.00, null,
+ -0.95, 992.31);
+ checkInst(model, 184, 184, LocalDate.of(2019, 7, 4), 17, 183, 50.00, 50.00, null, 0.82258308, 41.13, 792.79, 0.90, 0.00, null,
+ -0.90, 992.31);
+ checkInst(model, 185, 185, LocalDate.of(2019, 7, 5), 16, 184, 50.00, 50.00, null, 0.82170565, 41.09, 743.63, 0.85, 0.00, null,
+ -0.85, 992.31);
+ checkInst(model, 186, 186, LocalDate.of(2019, 7, 6), 15, 185, 50.00, 50.00, null, 0.82082916, 41.04, 694.43, 0.79, 0.00, null,
+ -0.79, 992.31);
+ checkInst(model, 187, 187, LocalDate.of(2019, 7, 7), 14, 186, 50.00, 50.00, null, 0.81995360, 41.00, 645.17, 0.74, 0.00, null,
+ -0.74, 992.31);
+ checkInst(model, 188, 188, LocalDate.of(2019, 7, 8), 13, 187, 50.00, 50.00, null, 0.81907897, 40.95, 595.86, 0.69, 0.00, null,
+ -0.69, 992.31);
+ checkInst(model, 189, 189, LocalDate.of(2019, 7, 9), 12, 188, 50.00, 50.00, null, 0.81820528, 40.91, 546.49, 0.64, 0.00, null,
+ -0.64, 992.31);
+ checkInst(model, 190, 190, LocalDate.of(2019, 7, 10), 11, 189, 50.00, 50.00, null, 0.81733252, 40.87, 497.08, 0.58, 0.00, null,
+ -0.58, 992.31);
+ checkInst(model, 191, 191, LocalDate.of(2019, 7, 11), 10, 190, 50.00, 50.00, null, 0.81646069, 40.82, 447.61, 0.53, 0.00, null,
+ -0.53, 992.31);
+ checkInst(model, 192, 192, LocalDate.of(2019, 7, 12), 9, 191, 50.00, 50.00, null, 0.81558979, 40.78, 398.08, 0.48, 0.00, null,
+ -0.48, 992.31);
+ checkInst(model, 193, 193, LocalDate.of(2019, 7, 13), 8, 192, 50.00, 50.00, null, 0.81471983, 40.74, 348.51, 0.43, 0.00, null,
+ -0.43, 992.31);
+ checkInst(model, 194, 194, LocalDate.of(2019, 7, 14), 7, 193, 50.00, 50.00, null, 0.81385078, 40.69, 298.88, 0.37, 0.00, null,
+ -0.37, 992.31);
+ checkInst(model, 195, 195, LocalDate.of(2019, 7, 15), 6, 194, 50.00, 50.00, null, 0.81298267, 40.65, 249.20, 0.32, 0.00, null,
+ -0.32, 992.31);
+ checkInst(model, 196, 196, LocalDate.of(2019, 7, 16), 5, 195, 50.00, 50.00, null, 0.81211548, 40.61, 199.47, 0.27, 0.00, null,
+ -0.27, 992.31);
+ checkInst(model, 197, 197, LocalDate.of(2019, 7, 17), 4, 196, 50.00, 50.00, null, 0.81124922, 40.56, 149.68, 0.21, 0.00, null,
+ -0.21, 992.31);
+ checkInst(model, 198, 198, LocalDate.of(2019, 7, 18), 3, 197, 50.00, 50.00, null, 0.81038388, 40.52, 99.84, 0.16, 0.00, null, -0.16,
+ 992.31);
+ checkInst(model, 199, 199, LocalDate.of(2019, 7, 19), 2, 198, 50.00, 50.00, null, 0.80951946, 40.48, 49.95, 0.11, 0.00, null, -0.11,
+ 992.31);
+ checkInst(model, 200, 200, LocalDate.of(2019, 7, 20), 1, 199, 50.00, 50.00, null, 0.80865597, 40.43, 0.00, 0.05, 0.00, null, -0.05,
+ 992.31);
+
+ assertEquals(202, model.payments().size(), "disbursement + 200 regular + 1 additional");
+ checkInst(model, 201, 201, LocalDate.of(2019, 7, 21), 0, 200, null, 10.00, null, 0.80779339, 8.08, null, null, 0.00, null, null,
+ null);
+ }
+
+ @Test
+ void testNoPayment_term200_originationFee1000_netDisbursement9000_pay0_0_50() {
+ final ProjectedAmortizationScheduleModel model = generateModel();
+ calculator.applyPayment(model, EXPECTED_DISBURSEMENT_DATE.plusDays(1), BigDecimal.ZERO);
+ calculator.applyPayment(model, EXPECTED_DISBURSEMENT_DATE.plusDays(2), BigDecimal.ZERO);
+ calculator.applyPayment(model, EXPECTED_DISBURSEMENT_DATE.plusDays(3), new BigDecimal("50"));
+
+ checkInst(model, 0, 0, EXPECTED_DISBURSEMENT_DATE, 203, 0, -9000.00, null, null, 1.00000000, -9000.00, 9000.00, null, null, null,
+ null, 1000.00);
+
+ checkInst(model, 1, 1, LocalDate.of(2019, 1, 2), 202, 0, 50.00, 50.00, 0.00, 1.00000000, 0.00, 8959.61, 9.61, 9.61, 0.00, -9.61,
+ 1000.00);
+ checkInst(model, 2, 2, LocalDate.of(2019, 1, 3), 201, 0, 50.00, 50.00, 0.00, 1.00000000, 0.00, 8919.18, 9.57, 9.61, 0.00, -9.57,
+ 1000.00);
+ checkInst(model, 3, 3, LocalDate.of(2019, 1, 4), 200, 0, 50.00, 50.00, 50.00, 1.00000000, 50.00, 8878.70, 9.52, 9.61, 9.61, 0.09,
+ 990.39);
+
+ checkInst(model, 4, 4, LocalDate.of(2019, 1, 5), 199, 1, 50.00, 50.00, null, 0.99893332, 49.95, 8838.18, 9.48, 0.00, null, -9.48,
+ 990.39);
+ checkInst(model, 5, 5, LocalDate.of(2019, 1, 6), 198, 2, 50.00, 50.00, null, 0.99786779, 49.89, 8797.62, 9.44, 0.00, null, -9.44,
+ 990.39);
+ checkInst(model, 6, 6, LocalDate.of(2019, 1, 7), 197, 3, 50.00, 50.00, null, 0.99680339, 49.84, 8757.01, 9.39, 0.00, null, -9.39,
+ 990.39);
+ checkInst(model, 7, 7, LocalDate.of(2019, 1, 8), 196, 4, 50.00, 50.00, null, 0.99574012, 49.79, 8716.36, 9.35, 0.00, null, -9.35,
+ 990.39);
+ checkInst(model, 8, 8, LocalDate.of(2019, 1, 9), 195, 5, 50.00, 50.00, null, 0.99467799, 49.73, 8675.67, 9.31, 0.00, null, -9.31,
+ 990.39);
+ checkInst(model, 9, 9, LocalDate.of(2019, 1, 10), 194, 6, 50.00, 50.00, null, 0.99361699, 49.68, 8634.94, 9.26, 0.00, null, -9.26,
+ 990.39);
+ checkInst(model, 10, 10, LocalDate.of(2019, 1, 11), 193, 7, 50.00, 50.00, null, 0.99255712, 49.63, 8594.16, 9.22, 0.00, null, -9.22,
+ 990.39);
+ checkInst(model, 11, 11, LocalDate.of(2019, 1, 12), 192, 8, 50.00, 50.00, null, 0.99149839, 49.57, 8553.33, 9.18, 0.00, null, -9.18,
+ 990.39);
+ checkInst(model, 12, 12, LocalDate.of(2019, 1, 13), 191, 9, 50.00, 50.00, null, 0.99044078, 49.52, 8512.47, 9.13, 0.00, null, -9.13,
+ 990.39);
+ checkInst(model, 13, 13, LocalDate.of(2019, 1, 14), 190, 10, 50.00, 50.00, null, 0.98938430, 49.47, 8471.56, 9.09, 0.00, null,
+ -9.09, 990.39);
+ checkInst(model, 14, 14, LocalDate.of(2019, 1, 15), 189, 11, 50.00, 50.00, null, 0.98832895, 49.42, 8430.60, 9.05, 0.00, null,
+ -9.05, 990.39);
+ checkInst(model, 15, 15, LocalDate.of(2019, 1, 16), 188, 12, 50.00, 50.00, null, 0.98727472, 49.36, 8389.61, 9.00, 0.00, null,
+ -9.00, 990.39);
+ checkInst(model, 16, 16, LocalDate.of(2019, 1, 17), 187, 13, 50.00, 50.00, null, 0.98622162, 49.31, 8348.56, 8.96, 0.00, null,
+ -8.96, 990.39);
+ checkInst(model, 17, 17, LocalDate.of(2019, 1, 18), 186, 14, 50.00, 50.00, null, 0.98516964, 49.26, 8307.48, 8.91, 0.00, null,
+ -8.91, 990.39);
+ checkInst(model, 18, 18, LocalDate.of(2019, 1, 19), 185, 15, 50.00, 50.00, null, 0.98411879, 49.21, 8266.35, 8.87, 0.00, null,
+ -8.87, 990.39);
+ checkInst(model, 19, 19, LocalDate.of(2019, 1, 20), 184, 16, 50.00, 50.00, null, 0.98306905, 49.15, 8225.18, 8.83, 0.00, null,
+ -8.83, 990.39);
+ checkInst(model, 20, 20, LocalDate.of(2019, 1, 21), 183, 17, 50.00, 50.00, null, 0.98202044, 49.10, 8183.96, 8.78, 0.00, null,
+ -8.78, 990.39);
+ checkInst(model, 21, 21, LocalDate.of(2019, 1, 22), 182, 18, 50.00, 50.00, null, 0.98097294, 49.05, 8142.70, 8.74, 0.00, null,
+ -8.74, 990.39);
+ checkInst(model, 22, 22, LocalDate.of(2019, 1, 23), 181, 19, 50.00, 50.00, null, 0.97992656, 49.00, 8101.39, 8.69, 0.00, null,
+ -8.69, 990.39);
+ checkInst(model, 23, 23, LocalDate.of(2019, 1, 24), 180, 20, 50.00, 50.00, null, 0.97888129, 48.94, 8060.04, 8.65, 0.00, null,
+ -8.65, 990.39);
+ checkInst(model, 24, 24, LocalDate.of(2019, 1, 25), 179, 21, 50.00, 50.00, null, 0.97783715, 48.89, 8018.65, 8.61, 0.00, null,
+ -8.61, 990.39);
+ checkInst(model, 25, 25, LocalDate.of(2019, 1, 26), 178, 22, 50.00, 50.00, null, 0.97679411, 48.84, 7977.21, 8.56, 0.00, null,
+ -8.56, 990.39);
+ checkInst(model, 26, 26, LocalDate.of(2019, 1, 27), 177, 23, 50.00, 50.00, null, 0.97575219, 48.79, 7935.73, 8.52, 0.00, null,
+ -8.52, 990.39);
+ checkInst(model, 27, 27, LocalDate.of(2019, 1, 28), 176, 24, 50.00, 50.00, null, 0.97471138, 48.74, 7894.21, 8.47, 0.00, null,
+ -8.47, 990.39);
+ checkInst(model, 28, 28, LocalDate.of(2019, 1, 29), 175, 25, 50.00, 50.00, null, 0.97367168, 48.68, 7852.63, 8.43, 0.00, null,
+ -8.43, 990.39);
+ checkInst(model, 29, 29, LocalDate.of(2019, 1, 30), 174, 26, 50.00, 50.00, null, 0.97263309, 48.63, 7811.02, 8.39, 0.00, null,
+ -8.39, 990.39);
+ checkInst(model, 30, 30, LocalDate.of(2019, 1, 31), 173, 27, 50.00, 50.00, null, 0.97159560, 48.58, 7769.36, 8.34, 0.00, null,
+ -8.34, 990.39);
+ checkInst(model, 31, 31, LocalDate.of(2019, 2, 1), 172, 28, 50.00, 50.00, null, 0.97055922, 48.53, 7727.66, 8.30, 0.00, null, -8.30,
+ 990.39);
+ checkInst(model, 32, 32, LocalDate.of(2019, 2, 2), 171, 29, 50.00, 50.00, null, 0.96952395, 48.48, 7685.91, 8.25, 0.00, null, -8.25,
+ 990.39);
+ checkInst(model, 33, 33, LocalDate.of(2019, 2, 3), 170, 30, 50.00, 50.00, null, 0.96848979, 48.42, 7644.12, 8.21, 0.00, null, -8.21,
+ 990.39);
+ checkInst(model, 34, 34, LocalDate.of(2019, 2, 4), 169, 31, 50.00, 50.00, null, 0.96745672, 48.37, 7602.28, 8.16, 0.00, null, -8.16,
+ 990.39);
+ checkInst(model, 35, 35, LocalDate.of(2019, 2, 5), 168, 32, 50.00, 50.00, null, 0.96642476, 48.32, 7560.40, 8.12, 0.00, null, -8.12,
+ 990.39);
+ checkInst(model, 36, 36, LocalDate.of(2019, 2, 6), 167, 33, 50.00, 50.00, null, 0.96539390, 48.27, 7518.47, 8.07, 0.00, null, -8.07,
+ 990.39);
+ checkInst(model, 37, 37, LocalDate.of(2019, 2, 7), 166, 34, 50.00, 50.00, null, 0.96436413, 48.22, 7476.50, 8.03, 0.00, null, -8.03,
+ 990.39);
+ checkInst(model, 38, 38, LocalDate.of(2019, 2, 8), 165, 35, 50.00, 50.00, null, 0.96333547, 48.17, 7434.48, 7.98, 0.00, null, -7.98,
+ 990.39);
+ checkInst(model, 39, 39, LocalDate.of(2019, 2, 9), 164, 36, 50.00, 50.00, null, 0.96230790, 48.12, 7392.42, 7.94, 0.00, null, -7.94,
+ 990.39);
+ checkInst(model, 40, 40, LocalDate.of(2019, 2, 10), 163, 37, 50.00, 50.00, null, 0.96128143, 48.06, 7350.31, 7.89, 0.00, null,
+ -7.89, 990.39);
+ checkInst(model, 41, 41, LocalDate.of(2019, 2, 11), 162, 38, 50.00, 50.00, null, 0.96025606, 48.01, 7308.16, 7.85, 0.00, null,
+ -7.85, 990.39);
+ checkInst(model, 42, 42, LocalDate.of(2019, 2, 12), 161, 39, 50.00, 50.00, null, 0.95923178, 47.96, 7265.97, 7.80, 0.00, null,
+ -7.80, 990.39);
+ checkInst(model, 43, 43, LocalDate.of(2019, 2, 13), 160, 40, 50.00, 50.00, null, 0.95820859, 47.91, 7223.72, 7.76, 0.00, null,
+ -7.76, 990.39);
+ checkInst(model, 44, 44, LocalDate.of(2019, 2, 14), 159, 41, 50.00, 50.00, null, 0.95718649, 47.86, 7181.44, 7.71, 0.00, null,
+ -7.71, 990.39);
+ checkInst(model, 45, 45, LocalDate.of(2019, 2, 15), 158, 42, 50.00, 50.00, null, 0.95616548, 47.81, 7139.11, 7.67, 0.00, null,
+ -7.67, 990.39);
+ checkInst(model, 46, 46, LocalDate.of(2019, 2, 16), 157, 43, 50.00, 50.00, null, 0.95514557, 47.76, 7096.73, 7.62, 0.00, null,
+ -7.62, 990.39);
+ checkInst(model, 47, 47, LocalDate.of(2019, 2, 17), 156, 44, 50.00, 50.00, null, 0.95412674, 47.71, 7054.31, 7.58, 0.00, null,
+ -7.58, 990.39);
+ checkInst(model, 48, 48, LocalDate.of(2019, 2, 18), 155, 45, 50.00, 50.00, null, 0.95310899, 47.66, 7011.84, 7.53, 0.00, null,
+ -7.53, 990.39);
+ checkInst(model, 49, 49, LocalDate.of(2019, 2, 19), 154, 46, 50.00, 50.00, null, 0.95209233, 47.60, 6969.33, 7.49, 0.00, null,
+ -7.49, 990.39);
+ checkInst(model, 50, 50, LocalDate.of(2019, 2, 20), 153, 47, 50.00, 50.00, null, 0.95107676, 47.55, 6926.77, 7.44, 0.00, null,
+ -7.44, 990.39);
+ checkInst(model, 51, 51, LocalDate.of(2019, 2, 21), 152, 48, 50.00, 50.00, null, 0.95006227, 47.50, 6884.17, 7.40, 0.00, null,
+ -7.40, 990.39);
+ checkInst(model, 52, 52, LocalDate.of(2019, 2, 22), 151, 49, 50.00, 50.00, null, 0.94904886, 47.45, 6841.52, 7.35, 0.00, null,
+ -7.35, 990.39);
+ checkInst(model, 53, 53, LocalDate.of(2019, 2, 23), 150, 50, 50.00, 50.00, null, 0.94803653, 47.40, 6798.82, 7.31, 0.00, null,
+ -7.31, 990.39);
+ checkInst(model, 54, 54, LocalDate.of(2019, 2, 24), 149, 51, 50.00, 50.00, null, 0.94702529, 47.35, 6756.08, 7.26, 0.00, null,
+ -7.26, 990.39);
+ checkInst(model, 55, 55, LocalDate.of(2019, 2, 25), 148, 52, 50.00, 50.00, null, 0.94601512, 47.30, 6713.30, 7.21, 0.00, null,
+ -7.21, 990.39);
+ checkInst(model, 56, 56, LocalDate.of(2019, 2, 26), 147, 53, 50.00, 50.00, null, 0.94500603, 47.25, 6670.47, 7.17, 0.00, null,
+ -7.17, 990.39);
+ checkInst(model, 57, 57, LocalDate.of(2019, 2, 27), 146, 54, 50.00, 50.00, null, 0.94399801, 47.20, 6627.59, 7.12, 0.00, null,
+ -7.12, 990.39);
+ checkInst(model, 58, 58, LocalDate.of(2019, 2, 28), 145, 55, 50.00, 50.00, null, 0.94299107, 47.15, 6584.67, 7.08, 0.00, null,
+ -7.08, 990.39);
+ checkInst(model, 59, 59, LocalDate.of(2019, 3, 1), 144, 56, 50.00, 50.00, null, 0.94198521, 47.10, 6541.70, 7.03, 0.00, null, -7.03,
+ 990.39);
+ checkInst(model, 60, 60, LocalDate.of(2019, 3, 2), 143, 57, 50.00, 50.00, null, 0.94098042, 47.05, 6498.68, 6.99, 0.00, null, -6.99,
+ 990.39);
+ checkInst(model, 61, 61, LocalDate.of(2019, 3, 3), 142, 58, 50.00, 50.00, null, 0.93997669, 47.00, 6455.62, 6.94, 0.00, null, -6.94,
+ 990.39);
+ checkInst(model, 62, 62, LocalDate.of(2019, 3, 4), 141, 59, 50.00, 50.00, null, 0.93897404, 46.95, 6412.51, 6.89, 0.00, null, -6.89,
+ 990.39);
+ checkInst(model, 63, 63, LocalDate.of(2019, 3, 5), 140, 60, 50.00, 50.00, null, 0.93797246, 46.90, 6369.36, 6.85, 0.00, null, -6.85,
+ 990.39);
+ checkInst(model, 64, 64, LocalDate.of(2019, 3, 6), 139, 61, 50.00, 50.00, null, 0.93697195, 46.85, 6326.16, 6.80, 0.00, null, -6.80,
+ 990.39);
+ checkInst(model, 65, 65, LocalDate.of(2019, 3, 7), 138, 62, 50.00, 50.00, null, 0.93597251, 46.80, 6282.92, 6.76, 0.00, null, -6.76,
+ 990.39);
+ checkInst(model, 66, 66, LocalDate.of(2019, 3, 8), 137, 63, 50.00, 50.00, null, 0.93497413, 46.75, 6239.63, 6.71, 0.00, null, -6.71,
+ 990.39);
+ checkInst(model, 67, 67, LocalDate.of(2019, 3, 9), 136, 64, 50.00, 50.00, null, 0.93397681, 46.70, 6196.29, 6.66, 0.00, null, -6.66,
+ 990.39);
+ checkInst(model, 68, 68, LocalDate.of(2019, 3, 10), 135, 65, 50.00, 50.00, null, 0.93298056, 46.65, 6152.91, 6.62, 0.00, null,
+ -6.62, 990.39);
+ checkInst(model, 69, 69, LocalDate.of(2019, 3, 11), 134, 66, 50.00, 50.00, null, 0.93198538, 46.60, 6109.48, 6.57, 0.00, null,
+ -6.57, 990.39);
+ checkInst(model, 70, 70, LocalDate.of(2019, 3, 12), 133, 67, 50.00, 50.00, null, 0.93099125, 46.55, 6066.00, 6.52, 0.00, null,
+ -6.52, 990.39);
+ checkInst(model, 71, 71, LocalDate.of(2019, 3, 13), 132, 68, 50.00, 50.00, null, 0.92999818, 46.50, 6022.48, 6.48, 0.00, null,
+ -6.48, 990.39);
+ checkInst(model, 72, 72, LocalDate.of(2019, 3, 14), 131, 69, 50.00, 50.00, null, 0.92900618, 46.45, 5978.91, 6.43, 0.00, null,
+ -6.43, 990.39);
+ checkInst(model, 73, 73, LocalDate.of(2019, 3, 15), 130, 70, 50.00, 50.00, null, 0.92801523, 46.40, 5935.29, 6.38, 0.00, null,
+ -6.38, 990.39);
+ checkInst(model, 74, 74, LocalDate.of(2019, 3, 16), 129, 71, 50.00, 50.00, null, 0.92702534, 46.35, 5891.63, 6.34, 0.00, null,
+ -6.34, 990.39);
+ checkInst(model, 75, 75, LocalDate.of(2019, 3, 17), 128, 72, 50.00, 50.00, null, 0.92603650, 46.30, 5847.92, 6.29, 0.00, null,
+ -6.29, 990.39);
+ checkInst(model, 76, 76, LocalDate.of(2019, 3, 18), 127, 73, 50.00, 50.00, null, 0.92504872, 46.25, 5804.17, 6.24, 0.00, null,
+ -6.24, 990.39);
+ checkInst(model, 77, 77, LocalDate.of(2019, 3, 19), 126, 74, 50.00, 50.00, null, 0.92406200, 46.20, 5760.36, 6.20, 0.00, null,
+ -6.20, 990.39);
+ checkInst(model, 78, 78, LocalDate.of(2019, 3, 20), 125, 75, 50.00, 50.00, null, 0.92307632, 46.15, 5716.52, 6.15, 0.00, null,
+ -6.15, 990.39);
+ checkInst(model, 79, 79, LocalDate.of(2019, 3, 21), 124, 76, 50.00, 50.00, null, 0.92209170, 46.10, 5672.62, 6.10, 0.00, null,
+ -6.10, 990.39);
+ checkInst(model, 80, 80, LocalDate.of(2019, 3, 22), 123, 77, 50.00, 50.00, null, 0.92110813, 46.06, 5628.68, 6.06, 0.00, null,
+ -6.06, 990.39);
+ checkInst(model, 81, 81, LocalDate.of(2019, 3, 23), 122, 78, 50.00, 50.00, null, 0.92012560, 46.01, 5584.69, 6.01, 0.00, null,
+ -6.01, 990.39);
+ checkInst(model, 82, 82, LocalDate.of(2019, 3, 24), 121, 79, 50.00, 50.00, null, 0.91914413, 45.96, 5540.65, 5.96, 0.00, null,
+ -5.96, 990.39);
+ checkInst(model, 83, 83, LocalDate.of(2019, 3, 25), 120, 80, 50.00, 50.00, null, 0.91816370, 45.91, 5496.57, 5.92, 0.00, null,
+ -5.92, 990.39);
+ checkInst(model, 84, 84, LocalDate.of(2019, 3, 26), 119, 81, 50.00, 50.00, null, 0.91718432, 45.86, 5452.44, 5.87, 0.00, null,
+ -5.87, 990.39);
+ checkInst(model, 85, 85, LocalDate.of(2019, 3, 27), 118, 82, 50.00, 50.00, null, 0.91620598, 45.81, 5408.26, 5.82, 0.00, null,
+ -5.82, 990.39);
+ checkInst(model, 86, 86, LocalDate.of(2019, 3, 28), 117, 83, 50.00, 50.00, null, 0.91522868, 45.76, 5364.03, 5.78, 0.00, null,
+ -5.78, 990.39);
+ checkInst(model, 87, 87, LocalDate.of(2019, 3, 29), 116, 84, 50.00, 50.00, null, 0.91425243, 45.71, 5319.76, 5.73, 0.00, null,
+ -5.73, 990.39);
+ checkInst(model, 88, 88, LocalDate.of(2019, 3, 30), 115, 85, 50.00, 50.00, null, 0.91327722, 45.66, 5275.44, 5.68, 0.00, null,
+ -5.68, 990.39);
+ checkInst(model, 89, 89, LocalDate.of(2019, 3, 31), 114, 86, 50.00, 50.00, null, 0.91230305, 45.62, 5231.08, 5.63, 0.00, null,
+ -5.63, 990.39);
+ checkInst(model, 90, 90, LocalDate.of(2019, 4, 1), 113, 87, 50.00, 50.00, null, 0.91132992, 45.57, 5186.66, 5.59, 0.00, null, -5.59,
+ 990.39);
+ checkInst(model, 91, 91, LocalDate.of(2019, 4, 2), 112, 88, 50.00, 50.00, null, 0.91035783, 45.52, 5142.20, 5.54, 0.00, null, -5.54,
+ 990.39);
+ checkInst(model, 92, 92, LocalDate.of(2019, 4, 3), 111, 89, 50.00, 50.00, null, 0.90938677, 45.47, 5097.69, 5.49, 0.00, null, -5.49,
+ 990.39);
+ checkInst(model, 93, 93, LocalDate.of(2019, 4, 4), 110, 90, 50.00, 50.00, null, 0.90841675, 45.42, 5053.13, 5.44, 0.00, null, -5.44,
+ 990.39);
+ checkInst(model, 94, 94, LocalDate.of(2019, 4, 5), 109, 91, 50.00, 50.00, null, 0.90744776, 45.37, 5008.53, 5.40, 0.00, null, -5.40,
+ 990.39);
+ checkInst(model, 95, 95, LocalDate.of(2019, 4, 6), 108, 92, 50.00, 50.00, null, 0.90647981, 45.32, 4963.88, 5.35, 0.00, null, -5.35,
+ 990.39);
+ checkInst(model, 96, 96, LocalDate.of(2019, 4, 7), 107, 93, 50.00, 50.00, null, 0.90551289, 45.28, 4919.18, 5.30, 0.00, null, -5.30,
+ 990.39);
+ checkInst(model, 97, 97, LocalDate.of(2019, 4, 8), 106, 94, 50.00, 50.00, null, 0.90454700, 45.23, 4874.43, 5.25, 0.00, null, -5.25,
+ 990.39);
+ checkInst(model, 98, 98, LocalDate.of(2019, 4, 9), 105, 95, 50.00, 50.00, null, 0.90358215, 45.18, 4829.64, 5.20, 0.00, null, -5.20,
+ 990.39);
+ checkInst(model, 99, 99, LocalDate.of(2019, 4, 10), 104, 96, 50.00, 50.00, null, 0.90261832, 45.13, 4784.79, 5.16, 0.00, null,
+ -5.16, 990.39);
+ checkInst(model, 100, 100, LocalDate.of(2019, 4, 11), 103, 97, 50.00, 50.00, null, 0.90165552, 45.08, 4739.90, 5.11, 0.00, null,
+ -5.11, 990.39);
+ checkInst(model, 101, 101, LocalDate.of(2019, 4, 12), 102, 98, 50.00, 50.00, null, 0.90069374, 45.03, 4694.96, 5.06, 0.00, null,
+ -5.06, 990.39);
+ checkInst(model, 102, 102, LocalDate.of(2019, 4, 13), 101, 99, 50.00, 50.00, null, 0.89973299, 44.99, 4649.98, 5.01, 0.00, null,
+ -5.01, 990.39);
+ checkInst(model, 103, 103, LocalDate.of(2019, 4, 14), 100, 100, 50.00, 50.00, null, 0.89877327, 44.94, 4604.94, 4.97, 0.00, null,
+ -4.97, 990.39);
+ checkInst(model, 104, 104, LocalDate.of(2019, 4, 15), 99, 101, 50.00, 50.00, null, 0.89781457, 44.89, 4559.86, 4.92, 0.00, null,
+ -4.92, 990.39);
+ checkInst(model, 105, 105, LocalDate.of(2019, 4, 16), 98, 102, 50.00, 50.00, null, 0.89685689, 44.84, 4514.73, 4.87, 0.00, null,
+ -4.87, 990.39);
+ checkInst(model, 106, 106, LocalDate.of(2019, 4, 17), 97, 103, 50.00, 50.00, null, 0.89590024, 44.80, 4469.55, 4.82, 0.00, null,
+ -4.82, 990.39);
+ checkInst(model, 107, 107, LocalDate.of(2019, 4, 18), 96, 104, 50.00, 50.00, null, 0.89494460, 44.75, 4424.32, 4.77, 0.00, null,
+ -4.77, 990.39);
+ checkInst(model, 108, 108, LocalDate.of(2019, 4, 19), 95, 105, 50.00, 50.00, null, 0.89398999, 44.70, 4379.05, 4.72, 0.00, null,
+ -4.72, 990.39);
+ checkInst(model, 109, 109, LocalDate.of(2019, 4, 20), 94, 106, 50.00, 50.00, null, 0.89303639, 44.65, 4333.72, 4.68, 0.00, null,
+ -4.68, 990.39);
+ checkInst(model, 110, 110, LocalDate.of(2019, 4, 21), 93, 107, 50.00, 50.00, null, 0.89208381, 44.60, 4288.35, 4.63, 0.00, null,
+ -4.63, 990.39);
+ checkInst(model, 111, 111, LocalDate.of(2019, 4, 22), 92, 108, 50.00, 50.00, null, 0.89113225, 44.56, 4242.93, 4.58, 0.00, null,
+ -4.58, 990.39);
+ checkInst(model, 112, 112, LocalDate.of(2019, 4, 23), 91, 109, 50.00, 50.00, null, 0.89018170, 44.51, 4197.46, 4.53, 0.00, null,
+ -4.53, 990.39);
+ checkInst(model, 113, 113, LocalDate.of(2019, 4, 24), 90, 110, 50.00, 50.00, null, 0.88923216, 44.46, 4151.94, 4.48, 0.00, null,
+ -4.48, 990.39);
+ checkInst(model, 114, 114, LocalDate.of(2019, 4, 25), 89, 111, 50.00, 50.00, null, 0.88828364, 44.41, 4106.38, 4.43, 0.00, null,
+ -4.43, 990.39);
+ checkInst(model, 115, 115, LocalDate.of(2019, 4, 26), 88, 112, 50.00, 50.00, null, 0.88733613, 44.37, 4060.76, 4.38, 0.00, null,
+ -4.38, 990.39);
+ checkInst(model, 116, 116, LocalDate.of(2019, 4, 27), 87, 113, 50.00, 50.00, null, 0.88638963, 44.32, 4015.10, 4.34, 0.00, null,
+ -4.34, 990.39);
+ checkInst(model, 117, 117, LocalDate.of(2019, 4, 28), 86, 114, 50.00, 50.00, null, 0.88544414, 44.27, 3969.38, 4.29, 0.00, null,
+ -4.29, 990.39);
+ checkInst(model, 118, 118, LocalDate.of(2019, 4, 29), 85, 115, 50.00, 50.00, null, 0.88449966, 44.22, 3923.62, 4.24, 0.00, null,
+ -4.24, 990.39);
+ checkInst(model, 119, 119, LocalDate.of(2019, 4, 30), 84, 116, 50.00, 50.00, null, 0.88355619, 44.18, 3877.81, 4.19, 0.00, null,
+ -4.19, 990.39);
+ checkInst(model, 120, 120, LocalDate.of(2019, 5, 1), 83, 117, 50.00, 50.00, null, 0.88261372, 44.13, 3831.95, 4.14, 0.00, null,
+ -4.14, 990.39);
+ checkInst(model, 121, 121, LocalDate.of(2019, 5, 2), 82, 118, 50.00, 50.00, null, 0.88167226, 44.08, 3786.04, 4.09, 0.00, null,
+ -4.09, 990.39);
+ checkInst(model, 122, 122, LocalDate.of(2019, 5, 3), 81, 119, 50.00, 50.00, null, 0.88073180, 44.04, 3740.09, 4.04, 0.00, null,
+ -4.04, 990.39);
+ checkInst(model, 123, 123, LocalDate.of(2019, 5, 4), 80, 120, 50.00, 50.00, null, 0.87979234, 43.99, 3694.08, 3.99, 0.00, null,
+ -3.99, 990.39);
+ checkInst(model, 124, 124, LocalDate.of(2019, 5, 5), 79, 121, 50.00, 50.00, null, 0.87885389, 43.94, 3648.03, 3.94, 0.00, null,
+ -3.94, 990.39);
+ checkInst(model, 125, 125, LocalDate.of(2019, 5, 6), 78, 122, 50.00, 50.00, null, 0.87791644, 43.90, 3601.92, 3.90, 0.00, null,
+ -3.90, 990.39);
+ checkInst(model, 126, 126, LocalDate.of(2019, 5, 7), 77, 123, 50.00, 50.00, null, 0.87697999, 43.85, 3555.77, 3.85, 0.00, null,
+ -3.85, 990.39);
+ checkInst(model, 127, 127, LocalDate.of(2019, 5, 8), 76, 124, 50.00, 50.00, null, 0.87604453, 43.80, 3509.56, 3.80, 0.00, null,
+ -3.80, 990.39);
+ checkInst(model, 128, 128, LocalDate.of(2019, 5, 9), 75, 125, 50.00, 50.00, null, 0.87511008, 43.76, 3463.31, 3.75, 0.00, null,
+ -3.75, 990.39);
+ checkInst(model, 129, 129, LocalDate.of(2019, 5, 10), 74, 126, 50.00, 50.00, null, 0.87417662, 43.71, 3417.01, 3.70, 0.00, null,
+ -3.70, 990.39);
+ checkInst(model, 130, 130, LocalDate.of(2019, 5, 11), 73, 127, 50.00, 50.00, null, 0.87324416, 43.66, 3370.66, 3.65, 0.00, null,
+ -3.65, 990.39);
+ checkInst(model, 131, 131, LocalDate.of(2019, 5, 12), 72, 128, 50.00, 50.00, null, 0.87231269, 43.62, 3324.26, 3.60, 0.00, null,
+ -3.60, 990.39);
+ checkInst(model, 132, 132, LocalDate.of(2019, 5, 13), 71, 129, 50.00, 50.00, null, 0.87138221, 43.57, 3277.81, 3.55, 0.00, null,
+ -3.55, 990.39);
+ checkInst(model, 133, 133, LocalDate.of(2019, 5, 14), 70, 130, 50.00, 50.00, null, 0.87045273, 43.52, 3231.31, 3.50, 0.00, null,
+ -3.50, 990.39);
+ checkInst(model, 134, 134, LocalDate.of(2019, 5, 15), 69, 131, 50.00, 50.00, null, 0.86952424, 43.48, 3184.76, 3.45, 0.00, null,
+ -3.45, 990.39);
+ checkInst(model, 135, 135, LocalDate.of(2019, 5, 16), 68, 132, 50.00, 50.00, null, 0.86859674, 43.43, 3138.16, 3.40, 0.00, null,
+ -3.40, 990.39);
+ checkInst(model, 136, 136, LocalDate.of(2019, 5, 17), 67, 133, 50.00, 50.00, null, 0.86767023, 43.38, 3091.51, 3.35, 0.00, null,
+ -3.35, 990.39);
+ checkInst(model, 137, 137, LocalDate.of(2019, 5, 18), 66, 134, 50.00, 50.00, null, 0.86674471, 43.34, 3044.81, 3.30, 0.00, null,
+ -3.30, 990.39);
+ checkInst(model, 138, 138, LocalDate.of(2019, 5, 19), 65, 135, 50.00, 50.00, null, 0.86582017, 43.29, 2998.06, 3.25, 0.00, null,
+ -3.25, 990.39);
+ checkInst(model, 139, 139, LocalDate.of(2019, 5, 20), 64, 136, 50.00, 50.00, null, 0.86489662, 43.24, 2951.26, 3.20, 0.00, null,
+ -3.20, 990.39);
+ checkInst(model, 140, 140, LocalDate.of(2019, 5, 21), 63, 137, 50.00, 50.00, null, 0.86397406, 43.20, 2904.42, 3.15, 0.00, null,
+ -3.15, 990.39);
+ checkInst(model, 141, 141, LocalDate.of(2019, 5, 22), 62, 138, 50.00, 50.00, null, 0.86305248, 43.15, 2857.52, 3.10, 0.00, null,
+ -3.10, 990.39);
+ checkInst(model, 142, 142, LocalDate.of(2019, 5, 23), 61, 139, 50.00, 50.00, null, 0.86213188, 43.11, 2810.57, 3.05, 0.00, null,
+ -3.05, 990.39);
+ checkInst(model, 143, 143, LocalDate.of(2019, 5, 24), 60, 140, 50.00, 50.00, null, 0.86121227, 43.06, 2763.57, 3.00, 0.00, null,
+ -3.00, 990.39);
+ checkInst(model, 144, 144, LocalDate.of(2019, 5, 25), 59, 141, 50.00, 50.00, null, 0.86029363, 43.01, 2716.52, 2.95, 0.00, null,
+ -2.95, 990.39);
+ checkInst(model, 145, 145, LocalDate.of(2019, 5, 26), 58, 142, 50.00, 50.00, null, 0.85937598, 42.97, 2669.42, 2.90, 0.00, null,
+ -2.90, 990.39);
+ checkInst(model, 146, 146, LocalDate.of(2019, 5, 27), 57, 143, 50.00, 50.00, null, 0.85845930, 42.92, 2622.27, 2.85, 0.00, null,
+ -2.85, 990.39);
+ checkInst(model, 147, 147, LocalDate.of(2019, 5, 28), 56, 144, 50.00, 50.00, null, 0.85754361, 42.88, 2575.07, 2.80, 0.00, null,
+ -2.80, 990.39);
+ checkInst(model, 148, 148, LocalDate.of(2019, 5, 29), 55, 145, 50.00, 50.00, null, 0.85662889, 42.83, 2527.82, 2.75, 0.00, null,
+ -2.75, 990.39);
+ checkInst(model, 149, 149, LocalDate.of(2019, 5, 30), 54, 146, 50.00, 50.00, null, 0.85571514, 42.79, 2480.52, 2.70, 0.00, null,
+ -2.70, 990.39);
+ checkInst(model, 150, 150, LocalDate.of(2019, 5, 31), 53, 147, 50.00, 50.00, null, 0.85480237, 42.74, 2433.17, 2.65, 0.00, null,
+ -2.65, 990.39);
+ checkInst(model, 151, 151, LocalDate.of(2019, 6, 1), 52, 148, 50.00, 50.00, null, 0.85389057, 42.69, 2385.77, 2.60, 0.00, null,
+ -2.60, 990.39);
+ checkInst(model, 152, 152, LocalDate.of(2019, 6, 2), 51, 149, 50.00, 50.00, null, 0.85297975, 42.65, 2338.31, 2.55, 0.00, null,
+ -2.55, 990.39);
+ checkInst(model, 153, 153, LocalDate.of(2019, 6, 3), 50, 150, 50.00, 50.00, null, 0.85206990, 42.60, 2290.81, 2.50, 0.00, null,
+ -2.50, 990.39);
+ checkInst(model, 154, 154, LocalDate.of(2019, 6, 4), 49, 151, 50.00, 50.00, null, 0.85116101, 42.56, 2243.26, 2.45, 0.00, null,
+ -2.45, 990.39);
+ checkInst(model, 155, 155, LocalDate.of(2019, 6, 5), 48, 152, 50.00, 50.00, null, 0.85025310, 42.51, 2195.65, 2.40, 0.00, null,
+ -2.40, 990.39);
+ checkInst(model, 156, 156, LocalDate.of(2019, 6, 6), 47, 153, 50.00, 50.00, null, 0.84934616, 42.47, 2148.00, 2.34, 0.00, null,
+ -2.34, 990.39);
+ checkInst(model, 157, 157, LocalDate.of(2019, 6, 7), 46, 154, 50.00, 50.00, null, 0.84844018, 42.42, 2100.29, 2.29, 0.00, null,
+ -2.29, 990.39);
+ checkInst(model, 158, 158, LocalDate.of(2019, 6, 8), 45, 155, 50.00, 50.00, null, 0.84753517, 42.38, 2052.53, 2.24, 0.00, null,
+ -2.24, 990.39);
+ checkInst(model, 159, 159, LocalDate.of(2019, 6, 9), 44, 156, 50.00, 50.00, null, 0.84663113, 42.33, 2004.73, 2.19, 0.00, null,
+ -2.19, 990.39);
+ checkInst(model, 160, 160, LocalDate.of(2019, 6, 10), 43, 157, 50.00, 50.00, null, 0.84572805, 42.29, 1956.87, 2.14, 0.00, null,
+ -2.14, 990.39);
+ checkInst(model, 161, 161, LocalDate.of(2019, 6, 11), 42, 158, 50.00, 50.00, null, 0.84482593, 42.24, 1908.96, 2.09, 0.00, null,
+ -2.09, 990.39);
+ checkInst(model, 162, 162, LocalDate.of(2019, 6, 12), 41, 159, 50.00, 50.00, null, 0.84392477, 42.20, 1860.99, 2.04, 0.00, null,
+ -2.04, 990.39);
+ checkInst(model, 163, 163, LocalDate.of(2019, 6, 13), 40, 160, 50.00, 50.00, null, 0.84302458, 42.15, 1812.98, 1.99, 0.00, null,
+ -1.99, 990.39);
+ checkInst(model, 164, 164, LocalDate.of(2019, 6, 14), 39, 161, 50.00, 50.00, null, 0.84212535, 42.11, 1764.92, 1.94, 0.00, null,
+ -1.94, 990.39);
+ checkInst(model, 165, 165, LocalDate.of(2019, 6, 15), 38, 162, 50.00, 50.00, null, 0.84122707, 42.06, 1716.80, 1.88, 0.00, null,
+ -1.88, 990.39);
+ checkInst(model, 166, 166, LocalDate.of(2019, 6, 16), 37, 163, 50.00, 50.00, null, 0.84032975, 42.02, 1668.64, 1.83, 0.00, null,
+ -1.83, 990.39);
+ checkInst(model, 167, 167, LocalDate.of(2019, 6, 17), 36, 164, 50.00, 50.00, null, 0.83943340, 41.97, 1620.42, 1.78, 0.00, null,
+ -1.78, 990.39);
+ checkInst(model, 168, 168, LocalDate.of(2019, 6, 18), 35, 165, 50.00, 50.00, null, 0.83853799, 41.93, 1572.15, 1.73, 0.00, null,
+ -1.73, 990.39);
+ checkInst(model, 169, 169, LocalDate.of(2019, 6, 19), 34, 166, 50.00, 50.00, null, 0.83764354, 41.88, 1523.83, 1.68, 0.00, null,
+ -1.68, 990.39);
+ checkInst(model, 170, 170, LocalDate.of(2019, 6, 20), 33, 167, 50.00, 50.00, null, 0.83675005, 41.84, 1475.45, 1.63, 0.00, null,
+ -1.63, 990.39);
+ checkInst(model, 171, 171, LocalDate.of(2019, 6, 21), 32, 168, 50.00, 50.00, null, 0.83585751, 41.79, 1427.03, 1.58, 0.00, null,
+ -1.58, 990.39);
+ checkInst(model, 172, 172, LocalDate.of(2019, 6, 22), 31, 169, 50.00, 50.00, null, 0.83496592, 41.75, 1378.55, 1.52, 0.00, null,
+ -1.52, 990.39);
+ checkInst(model, 173, 173, LocalDate.of(2019, 6, 23), 30, 170, 50.00, 50.00, null, 0.83407528, 41.70, 1330.02, 1.47, 0.00, null,
+ -1.47, 990.39);
+ checkInst(model, 174, 174, LocalDate.of(2019, 6, 24), 29, 171, 50.00, 50.00, null, 0.83318560, 41.66, 1281.45, 1.42, 0.00, null,
+ -1.42, 990.39);
+ checkInst(model, 175, 175, LocalDate.of(2019, 6, 25), 28, 172, 50.00, 50.00, null, 0.83229686, 41.61, 1232.81, 1.37, 0.00, null,
+ -1.37, 990.39);
+ checkInst(model, 176, 176, LocalDate.of(2019, 6, 26), 27, 173, 50.00, 50.00, null, 0.83140907, 41.57, 1184.13, 1.32, 0.00, null,
+ -1.32, 990.39);
+ checkInst(model, 177, 177, LocalDate.of(2019, 6, 27), 26, 174, 50.00, 50.00, null, 0.83052222, 41.53, 1135.39, 1.26, 0.00, null,
+ -1.26, 990.39);
+ checkInst(model, 178, 178, LocalDate.of(2019, 6, 28), 25, 175, 50.00, 50.00, null, 0.82963633, 41.48, 1086.61, 1.21, 0.00, null,
+ -1.21, 990.39);
+ checkInst(model, 179, 179, LocalDate.of(2019, 6, 29), 24, 176, 50.00, 50.00, null, 0.82875137, 41.44, 1037.77, 1.16, 0.00, null,
+ -1.16, 990.39);
+ checkInst(model, 180, 180, LocalDate.of(2019, 6, 30), 23, 177, 50.00, 50.00, null, 0.82786736, 41.39, 988.88, 1.11, 0.00, null,
+ -1.11, 990.39);
+ checkInst(model, 181, 181, LocalDate.of(2019, 7, 1), 22, 178, 50.00, 50.00, null, 0.82698430, 41.35, 939.93, 1.06, 0.00, null,
+ -1.06, 990.39);
+ checkInst(model, 182, 182, LocalDate.of(2019, 7, 2), 21, 179, 50.00, 50.00, null, 0.82610217, 41.31, 890.93, 1.00, 0.00, null,
+ -1.00, 990.39);
+ checkInst(model, 183, 183, LocalDate.of(2019, 7, 3), 20, 180, 50.00, 50.00, null, 0.82522099, 41.26, 841.89, 0.95, 0.00, null,
+ -0.95, 990.39);
+ checkInst(model, 184, 184, LocalDate.of(2019, 7, 4), 19, 181, 50.00, 50.00, null, 0.82434075, 41.22, 792.79, 0.90, 0.00, null,
+ -0.90, 990.39);
+ checkInst(model, 185, 185, LocalDate.of(2019, 7, 5), 18, 182, 50.00, 50.00, null, 0.82346144, 41.17, 743.63, 0.85, 0.00, null,
+ -0.85, 990.39);
+ checkInst(model, 186, 186, LocalDate.of(2019, 7, 6), 17, 183, 50.00, 50.00, null, 0.82258308, 41.13, 694.43, 0.79, 0.00, null,
+ -0.79, 990.39);
+ checkInst(model, 187, 187, LocalDate.of(2019, 7, 7), 16, 184, 50.00, 50.00, null, 0.82170565, 41.09, 645.17, 0.74, 0.00, null,
+ -0.74, 990.39);
+ checkInst(model, 188, 188, LocalDate.of(2019, 7, 8), 15, 185, 50.00, 50.00, null, 0.82082916, 41.04, 595.86, 0.69, 0.00, null,
+ -0.69, 990.39);
+ checkInst(model, 189, 189, LocalDate.of(2019, 7, 9), 14, 186, 50.00, 50.00, null, 0.81995360, 41.00, 546.49, 0.64, 0.00, null,
+ -0.64, 990.39);
+ checkInst(model, 190, 190, LocalDate.of(2019, 7, 10), 13, 187, 50.00, 50.00, null, 0.81907897, 40.95, 497.08, 0.58, 0.00, null,
+ -0.58, 990.39);
+ checkInst(model, 191, 191, LocalDate.of(2019, 7, 11), 12, 188, 50.00, 50.00, null, 0.81820528, 40.91, 447.61, 0.53, 0.00, null,
+ -0.53, 990.39);
+ checkInst(model, 192, 192, LocalDate.of(2019, 7, 12), 11, 189, 50.00, 50.00, null, 0.81733252, 40.87, 398.08, 0.48, 0.00, null,
+ -0.48, 990.39);
+ checkInst(model, 193, 193, LocalDate.of(2019, 7, 13), 10, 190, 50.00, 50.00, null, 0.81646069, 40.82, 348.51, 0.43, 0.00, null,
+ -0.43, 990.39);
+ checkInst(model, 194, 194, LocalDate.of(2019, 7, 14), 9, 191, 50.00, 50.00, null, 0.81558979, 40.78, 298.88, 0.37, 0.00, null,
+ -0.37, 990.39);
+ checkInst(model, 195, 195, LocalDate.of(2019, 7, 15), 8, 192, 50.00, 50.00, null, 0.81471983, 40.74, 249.20, 0.32, 0.00, null,
+ -0.32, 990.39);
+ checkInst(model, 196, 196, LocalDate.of(2019, 7, 16), 7, 193, 50.00, 50.00, null, 0.81385078, 40.69, 199.47, 0.27, 0.00, null,
+ -0.27, 990.39);
+ checkInst(model, 197, 197, LocalDate.of(2019, 7, 17), 6, 194, 50.00, 50.00, null, 0.81298267, 40.65, 149.68, 0.21, 0.00, null,
+ -0.21, 990.39);
+ checkInst(model, 198, 198, LocalDate.of(2019, 7, 18), 5, 195, 50.00, 50.00, null, 0.81211548, 40.61, 99.84, 0.16, 0.00, null, -0.16,
+ 990.39);
+ checkInst(model, 199, 199, LocalDate.of(2019, 7, 19), 4, 196, 50.00, 50.00, null, 0.81124922, 40.56, 49.95, 0.11, 0.00, null, -0.11,
+ 990.39);
+ checkInst(model, 200, 200, LocalDate.of(2019, 7, 20), 3, 197, 50.00, 50.00, null, 0.81038388, 40.52, 0.00, 0.05, 0.00, null, -0.05,
+ 990.39);
+
+ assertEquals(203, model.payments().size(), "disbursement + 200 regular + 2 additional");
+ checkInst(model, 201, 201, LocalDate.of(2019, 7, 21), 2, 198, null, 50.00, null, 0.80951946, 40.48, null, null, 0.00, null, null,
+ null);
+ checkInst(model, 202, 202, LocalDate.of(2019, 7, 22), 1, 199, null, 50.00, null, 0.80865597, 40.43, null, null, 0.00, null, null,
+ null);
+ }
+
+ @Test
+ void testLessPayment_term10_originationFee50_netDisbursement450_pay40() {
+ final BigDecimal smallOriginationFee = new BigDecimal("50");
+ final BigDecimal smallNetDisbursement = new BigDecimal("450");
+ final ProjectedAmortizationScheduleModel initial = calculator.generateModel(smallOriginationFee, smallNetDisbursement, TPV, RATE,
+ DAY_COUNT, EXPECTED_DISBURSEMENT_DATE, MC, CURRENCY);
+ final ProjectedAmortizationScheduleModel model = calculator.addDisbursement(initial, smallOriginationFee, smallNetDisbursement,
+ EXPECTED_DISBURSEMENT_DATE);
+
+ calculator.applyPayment(model, EXPECTED_DISBURSEMENT_DATE.plusDays(1), new BigDecimal("40"));
+
+ assertEquals(12, model.payments().size(), "disbursement + 10 regular + 1 tail");
+
+ checkInst(model, 0, 0, EXPECTED_DISBURSEMENT_DATE, 11, 0, -450.00, null, null, 1.00000000, -450.00, 450.00, null, null, null, null,
+ 50.00);
+
+ checkInst(model, 1, 1, LocalDate.of(2019, 1, 2), 10, 0, 50.00, 50.00, 40.00, 1.00000000, 40.00, 408.83, 8.83, 7.07, 7.07, -1.77,
+ 42.93);
+ checkInst(model, 2, 2, LocalDate.of(2019, 1, 3), 9, 1, 50.00, 50.00, null, 0.98074794, 49.04, 366.86, 8.03, 0.00, null, -8.03,
+ 42.93);
+ checkInst(model, 3, 3, LocalDate.of(2019, 1, 4), 8, 2, 50.00, 50.00, null, 0.96186652, 48.09, 324.06, 7.20, 0.00, null, -7.20,
+ 42.93);
+ checkInst(model, 4, 4, LocalDate.of(2019, 1, 5), 7, 3, 50.00, 50.00, null, 0.94334860, 47.17, 280.42, 6.36, 0.00, null, -6.36,
+ 42.93);
+ checkInst(model, 5, 5, LocalDate.of(2019, 1, 6), 6, 4, 50.00, 50.00, null, 0.92518720, 46.26, 235.93, 5.50, 0.00, null, -5.50,
+ 42.93);
+ checkInst(model, 6, 6, LocalDate.of(2019, 1, 7), 5, 5, 50.00, 50.00, null, 0.90737544, 45.37, 190.56, 4.63, 0.00, null, -4.63,
+ 42.93);
+ checkInst(model, 7, 7, LocalDate.of(2019, 1, 8), 4, 6, 50.00, 50.00, null, 0.88990659, 44.50, 144.30, 3.74, 0.00, null, -3.74,
+ 42.93);
+ checkInst(model, 8, 8, LocalDate.of(2019, 1, 9), 3, 7, 50.00, 50.00, null, 0.87277405, 43.64, 97.13, 2.83, 0.00, null, -2.83,
+ 42.93);
+ checkInst(model, 9, 9, LocalDate.of(2019, 1, 10), 2, 8, 50.00, 50.00, null, 0.85597135, 42.80, 49.04, 1.91, 0.00, null, -1.91,
+ 42.93);
+ checkInst(model, 10, 10, LocalDate.of(2019, 1, 11), 1, 9, 50.00, 50.00, null, 0.83949214, 41.97, 0.00, 0.96, 0.00, null, -0.96,
+ 42.93);
+
+ checkInst(model, 11, 11, LocalDate.of(2019, 1, 12), 0, 10, null, 10.00, null, 0.82333018, 8.23, null, null, 0.00, null, null, null);
+ }
+
+ @Test
+ void testExcessPayment_term10_originationFee50_netDisbursement450_pay110() {
+ final BigDecimal smallOriginationFee = new BigDecimal("50");
+ final BigDecimal smallNetDisbursement = new BigDecimal("450");
+ final ProjectedAmortizationScheduleModel initial = calculator.generateModel(smallOriginationFee, smallNetDisbursement, TPV, RATE,
+ DAY_COUNT, EXPECTED_DISBURSEMENT_DATE, MC, CURRENCY);
+ final ProjectedAmortizationScheduleModel model = calculator.addDisbursement(initial, smallOriginationFee, smallNetDisbursement,
+ EXPECTED_DISBURSEMENT_DATE);
+
+ calculator.applyPayment(model, EXPECTED_DISBURSEMENT_DATE.plusDays(1), new BigDecimal("110"));
+
+ assertEquals(10, model.payments().size(), "disbursement + 9 regular (period 10 removed, forecast was 0)");
+
+ checkInst(model, 0, 0, EXPECTED_DISBURSEMENT_DATE, 11, 0, -450.00, null, null, 1.00000000, -450.00, 450.00, null, null, null, null,
+ 50.00);
+
+ checkInst(model, 1, 1, LocalDate.of(2019, 1, 2), 10, 0, 50.00, 50.00, 110.00, 1.00000000, 110.00, 408.83, 8.83, 18.30, 18.30, 9.47,
+ 31.70);
+ checkInst(model, 2, 2, LocalDate.of(2019, 1, 3), 9, 1, 50.00, 50.00, null, 0.98074794, 49.04, 366.86, 8.03, 0.00, null, -8.03,
+ 31.70);
+ checkInst(model, 3, 3, LocalDate.of(2019, 1, 4), 8, 2, 50.00, 50.00, null, 0.96186652, 48.09, 324.06, 7.20, 0.00, null, -7.20,
+ 31.70);
+ checkInst(model, 4, 4, LocalDate.of(2019, 1, 5), 7, 3, 50.00, 50.00, null, 0.94334860, 47.17, 280.42, 6.36, 0.00, null, -6.36,
+ 31.70);
+ checkInst(model, 5, 5, LocalDate.of(2019, 1, 6), 6, 4, 50.00, 50.00, null, 0.92518720, 46.26, 235.93, 5.50, 0.00, null, -5.50,
+ 31.70);
+ checkInst(model, 6, 6, LocalDate.of(2019, 1, 7), 5, 5, 50.00, 50.00, null, 0.90737544, 45.37, 190.56, 4.63, 0.00, null, -4.63,
+ 31.70);
+ checkInst(model, 7, 7, LocalDate.of(2019, 1, 8), 4, 6, 50.00, 50.00, null, 0.88990659, 44.50, 144.30, 3.74, 0.00, null, -3.74,
+ 31.70);
+ checkInst(model, 8, 8, LocalDate.of(2019, 1, 9), 3, 7, 50.00, 50.00, null, 0.87277405, 43.64, 97.13, 2.83, 0.00, null, -2.83,
+ 31.70);
+ checkInst(model, 9, 9, LocalDate.of(2019, 1, 10), 2, 8, 50.00, 40.00, null, 0.85597135, 34.24, 49.04, 1.91, 0.00, null, -1.91,
+ 31.70);
+ }
+
+ private ProjectedAmortizationScheduleModel generateModel() {
+ final ProjectedAmortizationScheduleModel model = calculator.generateModel(ORIGINATION_FEE, NET_DISBURSEMENT, TPV, RATE, DAY_COUNT,
+ EXPECTED_DISBURSEMENT_DATE, MC, CURRENCY);
+ return calculator.addDisbursement(model, ORIGINATION_FEE, NET_DISBURSEMENT, EXPECTED_DISBURSEMENT_DATE);
+ }
+
+ private void checkInst(final ProjectedAmortizationScheduleModel model, final int index, final int expectedNo,
+ final LocalDate expectedDate, final long expectedCount, final long expectedPaymentsLeft, final Double expectedPayment,
+ final Double expectedForecastPayment, final Double expectedActualPayment, final Double expectedDiscountFactor,
+ final Double expectedNpvValue, final Double expectedBalance, final Double expectedAmortization,
+ final Double expectedNetAmortization, final Double expectedActualAmortization, final Double expectedIncomeModification,
+ final Double expectedDeferredBalance) {
+ final ProjectedPayment inst = model.payments().get(index);
+ final String p = "inst " + expectedNo + ": ";
+
+ assertEquals(expectedNo, inst.paymentNo(), p + "paymentNo");
+ assertEquals(expectedDate, inst.date(), p + "date");
+ assertEquals(expectedCount, inst.count(), p + "count");
+ assertEquals(expectedPaymentsLeft, inst.paymentsLeft(), p + "paymentsLeft");
+ assertMoneyValue(expectedPayment, inst.expectedPaymentAmount(), 2, p + "expectedPayment");
+ assertMoneyValue(expectedForecastPayment, inst.forecastPaymentAmount(), 2, p + "forecastPayment");
+ assertMoneyValue(expectedActualPayment, inst.actualPaymentAmount(), 2, p + "actualPayment");
+ assertValue(expectedDiscountFactor, inst.discountFactor(), 8, p + "discountFactor");
+ assertMoneyValue(expectedNpvValue, inst.npvValue(), 2, p + "npvValue");
+ assertMoneyValue(expectedBalance, inst.balance(), 2, p + "balance");
+ assertMoneyValue(expectedAmortization, inst.expectedAmortizationAmount(), 2, p + "expectedAmortization");
+ assertMoneyValue(expectedNetAmortization, inst.netAmortizationAmount(), 2, p + "netAmortization");
+ assertMoneyValue(expectedActualAmortization, inst.actualAmortizationAmount(), 2, p + "actualAmort");
+ assertMoneyValue(expectedIncomeModification, inst.incomeModification(), 2, p + "incomeModification");
+ assertMoneyValue(expectedDeferredBalance, inst.deferredBalance(), 2, p + "deferredBalance");
+ }
+
+ private void assertMoneyValue(final Double expected, final Money actual, final int scale, final String msg) {
+ if (expected == null) {
+ assertNull(actual, msg);
+ return;
+ }
+ final BigDecimal exp = BigDecimal.valueOf(expected).setScale(scale, RoundingMode.HALF_UP);
+ final BigDecimal act = actual.getAmount().setScale(scale, RoundingMode.HALF_UP);
+ assertEquals(0, exp.compareTo(act), msg + " — expected: " + exp + ", actual: " + act);
+ }
+
+ private void assertValue(final Double expected, final BigDecimal actual, final int scale, final String msg) {
+ if (expected == null) {
+ assertNull(actual, msg);
+ return;
+ }
+ final BigDecimal exp = BigDecimal.valueOf(expected).setScale(scale, RoundingMode.HALF_UP);
+ final BigDecimal act = actual.setScale(scale, RoundingMode.HALF_UP);
+ assertEquals(0, exp.compareTo(act), msg + " — expected: " + exp + ", actual: " + act);
+ }
+
+}