[ 
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)

Reply via email to