[
https://issues.apache.org/jira/browse/FINERACT-3201?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Senesh Perera reassigned FINERACT-3201:
---------------------------------------
Assignee: Senesh Perera
> Define how a per-day interest rate is converted when days in year is ACTUAL
> (AprCalculator)
> -------------------------------------------------------------------------------------------
>
> Key: FINERACT-3201
> URL: https://issues.apache.org/jira/browse/FINERACT-3201
> Project: Apache Fineract
> Issue Type: Improvement
> Components: Loan
> Affects Versions: 1.16.1
> Reporter: Senesh Perera
> Assignee: Senesh Perera
> Priority: Minor
>
> h3. Background
> Split out of FINERACT-3194 as suggested in review.
> Both the progressive engine (FINERACT-2197) and the cumulative engine (after
> FINERACT-3049 / PR #6827) calculate DAILY + ACTUAL interest by starting from
> the stored *annual nominal interest rate* and dividing by the actual days in
> the year (366 in a leap year, 365 otherwise).
> However, when a user enters an interest rate *per day*
> ({{interestRateFrequencyType = DAYS}}, or {{WHOLE_TERM}} with {{DAYS}}
> repayment frequency), {{AprCalculator.calculateFrom()}} always derives the
> stored annual nominal rate using a fixed *365* days. Consequently, in a leap
> year, the engine multiplies by 365 on input and divides by 366 during
> schedule generation, charging slightly less than the entered per-day rate.
> This ticket tracks the design decision on what a per-day interest rate should
> mean when {{DaysInYearType}} is {{ACTUAL}}.
> h3. Code Analysis (on develop d63beed)
> *1. Annual nominal rate conversion ({{AprCalculator.java}})*
> * {{AprCalculator.calculateFrom()}} converts a periodic rate to an annual
> nominal rate:
> ** {{DAYS}}: {{rate * getDaysInYear(daysInYearType)}}
> ** {{WHOLE_TERM}} with {{DAYS}} repayment: {{(rate / numberOfPeriods) *
> getDaysInYear(daysInYearType)}}
> * For {{ACTUAL}}, {{getDaysInYear()}} delegates to
> {{PaymentPeriodsInOneYearCalculator.calculate(DAYS)}}, which returns a
> hardcoded {{365}} (it has no date context).
> *2. Why {{AprCalculator}} cannot simply take a date*
> * {{AprCalculator.calculateFrom()}} is called at loan application/schedule
> time ({{LoanScheduleAssembler.java:285}}), but also during loan product
> creation ({{LoanProductAssembler.java:138}}) and product updates
> ({{LoanProductRelatedDetailUpdateUtil.java:410}}).
> * Because loan products have no disbursement date, the stored annual nominal
> rate on the product must remain date-independent.
> *3. API vs. UI support for per-day rates*
> * *API:* {{LoanProductDataValidator.java}} (lines 620, 1776) validates
> {{interestRateFrequencyType}} with {{inMinMaxRange(0, 4)}}, accepting {{0}}
> ({{DAYS}}). {{LoanApplicationValidator}} and {{LoanScheduleValidator}} also
> accept it at the loan level.
> * *UI Template:* {{LoanDropdownReadPlatformServiceImpl.java}} (lines 113–117)
> only returns {{MONTHS}}, {{YEARS}}, and {{WHOLE_TERM}}.
> * *Progressive Engine:* {{ProgressiveEMICalculator.java}} has no guard
> rejecting {{DAYS}}; it consumes the stored annual nominal rate directly.
> h3. Verified Behavior & Examples
> *Case A: Progressive & Cumulative Loans (0.1% per day, 10,000 principal,
> ACTUAL, DAILY, 31-day period)*
> * {{AprCalculator}} stores the annual rate as {{0.1 * 365 = 36.5%}}.
> * In 2024 (leap year), the daily rate applied is {{36.5% / 366 =
> 0.09972678%}}.
> * In 2025 (non-leap year), the daily rate applied is {{36.5% / 365 = 0.1%}}.
> * *Note:* This already occurs on {{develop}} today for progressive loans;
> FINERACT-3049 makes cumulative loans match this behavior.
> || Period || Expected at 0.1% / day || Actual Schedule Output (Progressive
> today & Cumulative after FINERACT-3049) ||
> | 2024-01-01 to 2024-02-01 (leap year) | 310.00 | 309.15 |
> | 2023-12-15 to 2024-01-15 (crosses year end)* | 310.00 | 309.59 |
> | 2025-01-01 to 2025-02-01 (non-leap control) | 310.00 | 310.00 |
> _(*Assumes default {{isInterestRecognitionOnDisbursementDate = false}}; with
> {{true}}, the cross-year output is {{309.62}}.)_
> *Case B: Whole-Term Rate over Days (FINERACT-2492 scenario)*
> FINERACT-2492 tests a {{10%}} {{WHOLE_TERM}} rate on a 3-day {{FLAT}} loan of
> {{3,400}} with {{DAYS}} repayment (expecting ~{{340.00}} total interest).
> * {{AprCalculator}} rounds {{10 / 3}} to 8 decimals ({{3.33333333}}) and
> multiplies by {{365}}, storing an annual nominal rate of {{1216.66666545%}}.
> * In a 365-day year, the flat schedule rounds each daily installment to
> {{113.33}}, totaling *{{339.99}}* (1 cent below {{340.00}} due to the
> 8-decimal rounding).
> * Once flat interest becomes leap-year aware (FINERACT-3194), a 3-day loan in
> 2024 divides by {{366}}, rounding each daily installment to {{113.02}} and
> totaling *{{339.06}}* (~9.97% over the term instead of the entered 10%).
> h3. Options for Decision
> # *Keep and document (365-based nominal conversion):* Under {{ACTUAL}}, a
> per-day rate is defined as a nominal rate annualized over 365 days, yielding
> {{rate * 365 / 366}} per day in a leap year. Requires documentation and tests
> only (no calculation change).
> # *Apply the per-day rate directly in the engines:* When the rate is entered
> with {{DAYS}} (or {{WHOLE_TERM}} over {{DAYS}}), bypass the 365/366 annual
> rate round-trip and apply {{rate * actualDays}} directly in both cumulative
> and progressive calculators. Changes interest on existing per-day {{ACTUAL}}
> loans in leap years.
> # *Restrict the combination:* Reject {{interestRateFrequencyType = DAYS}}
> (and {{WHOLE_TERM}} with {{DAYS}} repayment) when {{daysInYearType = ACTUAL}}
> in the validator. Simple, though potentially breaking for API callers relying
> on it.
> h3. Questions for Maintainers
> # Which of the three options is preferred?
> # Is {{interestRateFrequencyType = DAYS}} intentionally supported by the API
> despite being omitted from the UI dropdown template?
> # Should a {{WHOLE_TERM}} rate always charge the exact entered percentage
> across the loan term regardless of leap years?
> # If Option 2 is chosen, should it apply only to newly created loans or also
> on recalculation of existing loans?
> h3. Out of Scope
> * {{WEEKS}} (* 52) and {{SEMI_MONTHLY}} (* 24) conversions in
> {{AprCalculator}} (purely nominal).
> * Flat interest leap-year implementation (tracked in FINERACT-3194).
> * Cumulative loan EMI (stays nominal 365-based by design, per FINERACT-3049).
> h3. Related Issues
> * FINERACT-3194: Flat interest: DAILY interest with ACTUAL days in year
> should be leap-year aware
> * FINERACT-3049: Cumulative loans: DAILY interest with ACTUAL days in year is
> not leap-year aware (PR #6827)
> * FINERACT-2197: Introduce a new type of ACTUAL during leap year for
> progressive loans
> * FINERACT-2492: AprCalculator produces incorrect annual nominal rate for
> WHOLE_TERM with DAYS
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)