Both repositories were queried by enterprise alone, so every reason code and every forecast point ever written came back. A run-287 model score was being rendered beside run-145 rule sentences, and one enterprise's six-month chart drew 102 points from seventeen different runs. Now read through findByEnterpriseIdAndRunId..., resolved from the newest run. The same unscoped query existed in EnterpriseAppService and is fixed there too. Also adds GET /officer/app-enrolled: which borrowers on this caller's book have a phone login. Not every borrower is enrolled and that is the design — the platform scores a book from core banking and UPI whether or not the owner installs anything — so the console needs to know rather than offering to open an app that does not exist. Scoped by intersecting with the caller's own book, so it cannot be used to discover that an id exists on another bank's. EnterpriseRow gains firstBreachHorizon, firstBreachMonth and topDriver so the dashboard can rank by lead time instead of by composite score. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
139 lines
4.3 KiB
Java
139 lines
4.3 KiB
Java
package com.lincfusion.udyamlinc.dto;
|
|
|
|
import com.lincfusion.udyamlinc.enterprise.Enterprise;
|
|
import com.lincfusion.udyamlinc.enterprise.LedgerEntry;
|
|
import com.lincfusion.udyamlinc.officer.FieldVisit;
|
|
import com.lincfusion.udyamlinc.risk.ForecastPoint;
|
|
import com.lincfusion.udyamlinc.risk.ReasonCode;
|
|
import com.lincfusion.udyamlinc.risk.RiskAlert;
|
|
import com.lincfusion.udyamlinc.risk.StressScore;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.util.List;
|
|
|
|
/** Response shapes. These mirror the interfaces in UdyamLinc-UI/src/lib/api.ts. */
|
|
public final class UdyamResponses {
|
|
|
|
private UdyamResponses() {
|
|
}
|
|
|
|
public record OfficerSessionResponse(
|
|
String token,
|
|
String tokenType,
|
|
String username,
|
|
String fullName,
|
|
String role,
|
|
List<String> blocks,
|
|
String branch) {
|
|
}
|
|
|
|
public record EnterpriseSessionResponse(
|
|
String token,
|
|
String tokenType,
|
|
String enterpriseId,
|
|
String name,
|
|
String nameHi,
|
|
String sector,
|
|
String block,
|
|
String lang) {
|
|
}
|
|
|
|
/** An enterprise flattened with its sector code, as the console's table expects. */
|
|
public record EnterpriseRow(
|
|
Long id,
|
|
String enterpriseId,
|
|
String name,
|
|
String nameHi,
|
|
String sector,
|
|
String block,
|
|
String district,
|
|
Integer ownerCount,
|
|
Integer vintageMonths,
|
|
BigDecimal loanOutstanding,
|
|
BigDecimal monthlyInstalment,
|
|
Integer score,
|
|
String band,
|
|
List<Integer> trend,
|
|
String lastUpdated,
|
|
|
|
/**
|
|
* Months until the downside case first fails to cover the instalment,
|
|
* or null if it never does over the horizon.
|
|
*
|
|
* <p>Here because lead time is the product. A dashboard that ranks by
|
|
* score alone shows what a bureau score already shows; ranking by when
|
|
* trouble arrives is the thing this platform can do that they cannot.
|
|
*/
|
|
Integer firstBreachHorizon,
|
|
String firstBreachMonth,
|
|
|
|
/** The top-ranked reason sentence from the run that produced the score. */
|
|
String topDriver,
|
|
String topDriverHi) {
|
|
}
|
|
|
|
/** Dashboard KPI tiles plus the enterprises-by-risk register. */
|
|
public record PortfolioResponse(
|
|
String block,
|
|
String month,
|
|
long total,
|
|
long totalDelta,
|
|
long red,
|
|
long redDelta,
|
|
long watch,
|
|
long watchDelta,
|
|
long standard,
|
|
long standardDelta,
|
|
int updatedMinutesAgo,
|
|
List<EnterpriseRow> rows) {
|
|
}
|
|
|
|
/** Everything the risk detail panel needs for one enterprise. */
|
|
public record EnterpriseDetailResponse(
|
|
Enterprise enterprise,
|
|
String sector,
|
|
String district,
|
|
StressScore score,
|
|
List<ReasonCode> reasons,
|
|
List<ForecastPoint> forecast,
|
|
List<RiskAlert> alerts,
|
|
List<FieldVisit> visits,
|
|
List<LedgerEntry> ledger) {
|
|
}
|
|
|
|
public record ForecastResponse(
|
|
String enterpriseId,
|
|
String modelVersion,
|
|
String engine,
|
|
int horizonMonths,
|
|
String breachMonth,
|
|
BigDecimal shortfall,
|
|
List<ForecastPoint> points) {
|
|
}
|
|
|
|
/** The enterprise app's Home tab in one call. */
|
|
public record AppHomeResponse(
|
|
Enterprise enterprise,
|
|
RiskAlert alert,
|
|
List<ForecastPoint> forecast,
|
|
BigDecimal todayIn,
|
|
BigDecimal todayOut,
|
|
String adviceEn,
|
|
String adviceHi,
|
|
String lastSyncedAt) {
|
|
}
|
|
|
|
public record SyncResultResponse(int accepted, int duplicates, String syncedAt) {
|
|
}
|
|
|
|
/** One playbook action, offered for a band. */
|
|
public record PlaybookActionResponse(
|
|
String code, String label, String labelHi, String appliesTo, String detail) {
|
|
}
|
|
|
|
/** Sector pack rollup for the Forecasts screen. */
|
|
public record SectorRollupResponse(
|
|
String sector, String label, long count, long red, long watch, long standard, int avgScore) {
|
|
}
|
|
}
|