Skip to content

FINERACT-2460: Add Client Performance API for real-time financial metrics#5410

Open
nidhiii128 wants to merge 1 commit intoapache:developfrom
nidhiii128:develop
Open

FINERACT-2460: Add Client Performance API for real-time financial metrics#5410
nidhiii128 wants to merge 1 commit intoapache:developfrom
nidhiii128:develop

Conversation

@nidhiii128
Copy link

Description

This project implements a robust API endpoint and service layer to retrieve and display performance-related data for a client within the Apache Fineract ecosystem. The implementation focuses on aggregating real-time financial metrics, including active loan counts and a comprehensive "Total Outstanding Balance" (Principal + Interest + Fees + Penalties) for active loan accounts.

Acceptance Criteria & Implementation Proof

  1. Integrate Client Profile Performance API with the app
    What I Did: Developed a RESTful endpoint GET /clients/{clientId}/performance within the ClientsApiResource class.
Screenshot 2026-01-28 212943 Proof: Successfully mapped the resource and confirmed accessibility via the terminal using curl.
  1. Fetch and display performance data for a client in the profile view
    What I Did: Implemented the retrieveClientPerformance method in the service layer to fetch real-time data from the database using an optimized SQL query.
Screenshot 2026-01-28 200929 Proof: The API successfully returns a JSON payload containing activeLoans and totalOutstandingBalance.
  1. Ensure data is updated in real-time or via scheduled sync
    What I Did: Utilized Fineract's JdbcTemplate to perform direct reads on the m_loan and m_client tables, ensuring the metrics reflect the current state of the database immediately upon request.
Screenshot 2026-01-28 211242 Proof: Manual verification through Swagger and database checks confirmed that balance changes are reflected as soon as loans are updated.
  1. Handle API failures gracefully with appropriate error messages
    What I Did: Implemented error handling to detect non-existent client IDs, throwing a ClientNotFoundException which results in a standard Fineract 404 error response.
Screenshot 2026-01-28 212445 Proof: Added logic to verify client existence before running the aggregation query, ensuring a clear error message is returned if the ID is invalid.
  1. Add unit tests and integration tests for the API layer
    What I Did: Created ClientPerformanceApiIntegrationTest.java using the RestAssured framework to automate the verification of the API.
Screenshot 2026-01-29 073849 ?[32m2026-01-29 02:06:36.116?[0;39m [Test worker] ?[34mINFO ?[0;39m ?[36mo.a.f.i.common.ClientHelper?[0;39m - ---------------------------------CREATING A CLIENT--------------------------------------------- ?[32m2026-01-29 02:06:36.130?[0;39m [Test worker] ?[34mINFO ?[0;39m ?[36mo.a.f.i.common.ClientHelper?[0;39m - TestClient Request : {"firstname":"Client_FirstName_Q528L","officeId":"1","dateFormat":"dd MMMM yyyy","externalId":"b92a1915-a58e-401e-96b4-45a089036319","active":"true","locale":"en","activationDate":"04 March 2011","legalFormId":1,"lastname":"Client_LastName_ILBP"} ?[32m2026-01-29 02:06:36.132?[0;39m [Test worker] ?[34mINFO ?[0;39m ?[36mo.a.f.integrationtests.common.Utils?[0;39m - JSON {"firstname":"Client_FirstName_Q528L","officeId":"1","dateFormat":"dd MMMM yyyy","externalId":"b92a1915-a58e-401e-96b4-45a089036319","active":"true","locale":"en","activationDate":"04 March 2011","legalFormId":1,"lastname":"Client_LastName_ILBP"}

Proof: The test suite executed with a 100% success rate.

  1. Confirm data matches the API response structure
    What I Did: Defined a dedicated Data Transfer Object (DTO), ClientPerformanceData, to ensure the response structure is consistent and types match the expected financial precision.
    Supporting Evidence:
    [Image 1: DTO Structure]: ClientPerformanceData.java showing the defined fields.
image [Image 2: Test Assertions]: assertEquals logic in the Integration Test. image [Image 3: Test Success]: The green "100% Success" bar from terminal/Gradle report. Screenshot 2026-01-29 071856 Proof: The integration test specifically asserts the values of activeLoans and totalOutstandingBalance against the JSON response.

Technical Summary of Financial Logic

The balance is aggregated using the following formula for all loans with Status 300 (Active):
TotalOutstanding = Principal_{derived} + Interest_{derived} + Fees_{derived} + Penalties_{derived}
Using the _derived columns from the m_loan table ensures that we are using pre-calculated, high-performance data rather than re-calculating the entire schedule on every API call.

Checklist

Please make sure these boxes are checked before submitting your pull request - thanks!

  • Write the commit message as per our guidelines
  • Acknowledge that we will not review PRs that are not passing the build ("green") - it is your responsibility to get a proposed PR to pass the build, not primarily the project's maintainers.
  • Create/update unit or integration tests for verifying the changes made.
  • Follow our coding conventions.
  • Add required Swagger annotation and update API documentation at fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm with details of any API changes
  • This PR must not be a "code dump". Large changes can be made in a branch, with assistance. Ask for help on the developer mailing list.

Your assigned reviewer(s) will follow our guidelines for code reviews.

@nidhiii128 nidhiii128 changed the title MIFOSAC-587 : Client Profile Performance API Integration FINERACT-0000 MIFOSAC-587 : Client Profile Performance API Integration Jan 29, 2026
@IOhacker
Copy link
Contributor

@nidhiii128 Apache Fineract != Mifos please join the Apache Fineract list, involve with the Apache Fineract community. If you are interested please open an account in Jira, then review if the item has not been previously reported if not then you can open a new ticket https://selfserve.apache.org/jira-account.html Read and understand how to submit contibutions to Apache Fineract

Copy link
Contributor

@IOhacker IOhacker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kindly see my comments

@nidhiii128
Copy link
Author

@nidhiii128 Apache Fineract != Mifos please join the Apache Fineract list, involve with the Apache Fineract community. If you are interested please open an account in Jira, then review if the item has not been previously reported if not then you can open a new ticket https://selfserve.apache.org/jira-account.html Read and understand how to submit contibutions to Apache Fineract

Really sorry to misunderstand things, will understand more about the fineract project and help to contribute. Thank you for your the information.

@IOhacker
Copy link
Contributor

Don worry, your effort can be applied using the proper Jira ticket also Apache Fineract will be a GSOC participant.

@nidhiii128 nidhiii128 changed the title FINERACT-0000 MIFOSAC-587 : Client Profile Performance API Integration FINERACT-2460 : Client Profile Performance API Integration Jan 29, 2026
Copy link
Contributor

@IOhacker IOhacker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nidhiii128 the PR and the commit must follow the title conventions, please use "Fineract-2460: Add Client Performance API for real-time financial metrics"

Squash and commit your changes because only 1 commit per PR is required.

Why not to use the https://{URL_BASE}/fineract-provider/api/v1/clients/{CLIENT_ID}/accounts endpoint ?

@nidhiii128 nidhiii128 changed the title FINERACT-2460 : Client Profile Performance API Integration Fineract-2460: Add Client Performance API for real-time financial metrics Jan 30, 2026
@nidhiii128 nidhiii128 changed the title Fineract-2460: Add Client Performance API for real-time financial metrics FINERACT-2460: Add Client Performance API for real-time financial metrics Jan 30, 2026
@nidhiii128 nidhiii128 changed the title FINERACT-2460: Add Client Performance API for real-time financial metrics Fineract-2460: Add Client Performance API for real-time financial metrics Jan 30, 2026
@nidhiii128
Copy link
Author

@nidhiii128 the PR and the commit must follow the title conventions, please use "Fineract-2460: Add Client Performance API for real-time financial metrics"

Squash and commit your changes because only 1 commit per PR is required.

Why not to use the https://{URL_BASE}/fineract-provider/api/v1/clients/{CLIENT_ID}/accounts endpoint ?

/accounts returns a large array of objects (full account summaries for every loan). my peformance endpoint returns exactly two numbers. Calculating the total on the database side (via SUM and COALESCE query) is significantly faster than fetching 50 loan objects and summing them in the Java frontend or mobile app. By returning exactly two numbers instead of a large object tree, we reduce the time-to-first-render for the Client Profile summary, which is critical for performance in low-bandwidth environments.

image

The endpoint returns groupLoanIndividualMonitoringAccounts and guarantorAccounts. If the user just wants to see their "Total Balance," they don't need to know about guarantor status. Even if these arrays were full of loans, the user would still be seeing 0.00$ for "Performance" because this endpoint doesn't sum up the totals for them.

@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Retrieve client performance metrics", description = "Returns a summary of active loans and outstanding balances for a specific client.")
@ApiResponses({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove ApiResponses anotation, It is not needed in newer java versions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only keep ApiResponse

final String sql = "SELECT " + "(SELECT COUNT(*) FROM m_loan WHERE client_id = c.id AND loan_status_id = 300) as activeLoans, "
+ "(SELECT COALESCE(SUM(principal_outstanding_derived + interest_outstanding_derived + fee_charges_outstanding_derived + penalty_charges_outstanding_derived), 0) "
+ " FROM m_loan WHERE client_id = c.id AND loan_status_id = 300) as totalOutstanding "
+ "FROM m_client c WHERE c.id = ?";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its a pure string concatenaton java 15 + can use textblock as it willl make SQL more readable

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the use of StringBuilder?

Copy link
Contributor

@Aman-Mittal Aman-Mittal Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StringBuilder will help if the sql written here was dynamic but i can see there is a simple static string. In that case there is no performance gain using this. This will make it harder to review lateron

Copy link
Contributor

@Aman-Mittal Aman-Mittal Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like this will be much better as it will compile only once

`
final String sql = """
SELECT
(SELECT COUNT(*)
FROM m_loan
WHERE client_id = c.id
AND loan_status_id = 300) AS activeLoans,
(SELECT COALESCE(
SUM(principal_outstanding_derived
+ interest_outstanding_derived
+ fee_charges_outstanding_derived
+ penalty_charges_outstanding_derived), 0)
FROM m_loan
WHERE client_id = c.id
AND loan_status_id = 300) AS totalOutstanding
FROM m_client c
WHERE c.id = ?
""";

`

final String sql = "SELECT " + "(SELECT COUNT(*) FROM m_loan WHERE client_id = c.id AND loan_status_id = 300) as activeLoans, "
+ "(SELECT COALESCE(SUM(principal_outstanding_derived + interest_outstanding_derived + fee_charges_outstanding_derived + penalty_charges_outstanding_derived), 0) "
+ " FROM m_loan WHERE client_id = c.id AND loan_status_id = 300) as totalOutstanding "
+ "FROM m_client c WHERE c.id = ?";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the use of StringBuilder?

@nidhiii128
Copy link
Author

nidhiii128 commented Jan 30, 2026

@IOhacker, regarding the StringBuilder, as it is traditionally used in Fineract to manage multi-line SQL. However, I believe Java 15 Text Blocks are the more technically sound choice here.
Since this SQL query is a static constant and doesn't require conditional appending, the Java compiler internizes the Text Block into the String Constant Pool. Using StringBuilder would introduce unnecessary heap allocation and runtime .append() calls for a string that never changes.
While Text Blocks eliminate the 'missing space' risk often found in String concatenation ("LINE1"+"LINE2"). It allows us to keep the SQL formatting identical to the source database schema, which simplifies future debugging of the SUM and COALESCE logic and also given that Fineract has upgraded Text Blocks helps reduce boilerplate code.

@IOhacker
Copy link
Contributor

@nidhiii128 why use Sql and why not JPQL/JQL?

@nidhiii128
Copy link
Author

nidhiii128 commented Jan 31, 2026

@nidhiii128 why use Sql and why not JPQL/JQL?
@IOhacker sql used here to adhere to fineracts's cors arch, providing better performace for read only aggregations by avoiding JPA persistence context overhead and ensuring direct, optimized streaming into DTOs ( ClientPerformanceData.java).

@nidhiii128
Copy link
Author

@Aman-Mittal I have updated the ClientsApiResource.java to remove the redundant @ApiResponses wrappers for methods with a single response code. I've also converted the SQL query in ClientReadPlatformServiceImpl.java into a Java 15 Text Block for better readability and verified the output locally via Swagger/curl. Ready for review!

@nidhiii128 nidhiii128 changed the title Fineract-2460: Add Client Performance API for real-time financial metrics FINERACT-2460: Add Client Performance API for real-time financial metrics Jan 31, 2026
@nidhiii128 nidhiii128 requested a review from IOhacker January 31, 2026 11:19
@IOhacker
Copy link
Contributor

@nidhiii128 fyi https://issues.apache.org/jira/browse/FINERACT-2022
@nidhiii128 this change have been tested in MariaDB and Postgresql?

@nidhiii128
Copy link
Author

@nidhiii128 fyi https://issues.apache.org/jira/browse/FINERACT-2022 @nidhiii128 this change have been tested in MariaDB and Postgresql?

I have verified that the SQL query uses only standard ANSI SQL functions (like SUM and JOIN) that are fully compatible with both MariaDB and PostgreSQL. I have avoided any database-specific functions (like date formatting) that caused issues in the past. I am also monitoring the PostgreSQL integration tests in GitHub Actions to ensure 100% compliance. Regarding FINERACT-2022, I understand that JdbcTemplate is being phased out for QueryDSL, and I will keep this architectural goal in mind for future contributions

} catch (EmptyResultDataAccessException e) {
throw new ClientNotFoundException(clientId,e);
}
}
Copy link
Contributor

@Aman-Mittal Aman-Mittal Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current implementation performs two queries and scans m_loan twice via correlated subqueries. This can be optimized into a single LEFT JOIN + aggregate, avoiding redundant DB round trips and duplicate table scans.

Why this is important? because it can degrade the performance if client have huge amount of data,
It is also recommended that indexes are maintained before using this type of aggregation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Aman-Mittal I have updated the implementation to use a single LEFT JOIN with GROUP BY as suggested. This has eliminated the redundant database round trips and the duplicate scans of the m_loan table. I have also verified the fix locally and ensured the exception chaining correctly preserves the root cause.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add appopriate index for this too? with compound index.

this will make this query faster.

you can check existing implementations in codebase

Copy link
Author

@nidhiii128 nidhiii128 Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Aman-Mittal , thank you for the feedback. I have updated the implementation with the following optimizations:

  1. Rewrote the logic to use a single LEFT JOIN with GROUP BY. This avoids redundant database round trips and eliminates the duplicate scans of the m_loan table.
  2. Added a Liquibase migration (0209_add_index_for_client_performance_api.xml) to create a compound index on m_loan(client_id, loan_status_id). This ensures the query remains performant even for clients with a large number of loan accounts. ( taking reference from the existing implementations in codebase)
  3. Refined the exception handling to use EmptyResultDataAccessException for detecting missing clients while preserving the ClientNotFoundException for API consistency.

Verified the changes locally with Checkstyle and compilation.

@nidhiii128 nidhiii128 force-pushed the develop branch 2 times, most recently from 27837c2 to abd31cc Compare January 31, 2026 18:23
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Contributor

@Aman-Mittal Aman-Mittal Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<!--

    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.

-->

add this here

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="1" author="nidhiii128">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

author will be fineract here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@nidhiii128 nidhiii128 force-pushed the develop branch 2 times, most recently from 1c26881 to 60b8f4e Compare February 1, 2026 09:30
Copy link
Contributor

@Aman-Mittal Aman-Mittal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nidhiii128
Copy link
Author

@IOhacker I have updated with all requested changes.
Regarding the build failure: The jib task is failing with an authorization error (Build image failed... credentials) because this is a fork. The code compilation and tests passed successfully. Could you please review?

@IOhacker
Copy link
Contributor

IOhacker commented Feb 2, 2026

@nidhiii128 Github Actions are running now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants