# CRED-6046: Bureau Surrogate Policy — Feature Documentation **Branch:** `CRED-6046-update-bureau-surrogate-policy` **Date:** 2026-03-30 --- ## Overview This feature adds **Bureau Surrogate Limit** as a new Table of Offers (TOF) logic type within the auto-decision / grade approval pipeline. When the BRE engine returns a `bureau_surrogate` rule with loan-type-specific limits, the system now uses those limits to cap the approved loan amount — potentially lowering it below what the standard residual-EMI flow would have approved. --- ## What Changed (Summary) | File | Change Type | |------|-------------| | `app/Http/Controllers/Grade/GradeController.php` | Core logic: extract bureau surrogate limits, compare against residual EMI, apply the lower cap | | `app/Http/Controllers/Admin/MerchantController.php` | Read bureau surrogate EMI details for merchant offer display | | `app/Http/Controllers/Admin/ConsumerApplicationController.php` | Cleanup: removed dead commented-out `checkCoAppRequire` method; added `user_id` to autoBRE logger | | `app/ApplicationEMICalculation.php` | Added `table_of_offers_logic`, `residual_emi`, `bureau_surrogate_amount` to `$fillable` | | `database/migrations/2026_03_30_180000_add_bureau_surrogate_to_emi_calculation_table.php` | New migration: adds `bureau_surrogate_amount` column & extends `table_of_offers_logic` enum | | `resources/views/admin/grade/edit_v1.blade.php` | UI: Personal Account Count (PAC) sub-modal for surrogate config | | `resources/views/admin/consumer-application/edit_v1.blade.php` | Bug fix: swapped `loanAmount` / `approvedAmount` variables in offer change alert | --- ## Detailed Changes ### 1. Database Migration **File:** `database/migrations/2026_03_30_180000_add_bureau_surrogate_to_emi_calculation_table.php` - Adds `bureau_surrogate_amount` (`UNSIGNED BIGINT`, nullable) column to the `emi_calculation` table after the `residual_emi` column. - Extends the `table_of_offers_logic` ENUM to include `'bureau_surrogate_limit'` alongside existing values (`residual_emi`, `minimum_tenure`, `power_saving`). ### 2. Model Update **File:** `app/ApplicationEMICalculation.php` - Added `table_of_offers_logic`, `residual_emi`, and `bureau_surrogate_amount` to the `$fillable` array so they can be mass-assigned. ### 3. GradeController — Core Logic **File:** `app/Http/Controllers/Grade/GradeController.php` #### 3.1 New Property - `public $bureauSurrogateLimits = []` — stores extracted bureau surrogate limit amounts for the current BRE run. #### 3.2 `multiApprovalCheck()` Refactor - **Before:** `passedPolicies` was a flat array of policy IDs. Policy filtering used `array_map` + `array_filter` in two passes. - **After:** `passedPolicies` is an associative array `[ policyId => policyObject ]` built via a single `array_reduce`. `filteredGradeRules` is derived immediately with `whereIn`. - Variables (`$allFailed`, `$response`, `$allSuccess`, `$success`, solar/sub-category flags) are now declared **before** the BRE result processing block for clearer scope. - Calls `$this->extractBureauSurrogateLimits($passedPolicies)` right after policy filtering to capture any bureau surrogate amounts from the BRE response. #### 3.3 New Method: `extractBureauSurrogateLimits(array $policies): array` - Iterates through all passed policies, finds the `bureau_surrogate` rule inside each policy's `rules` array. - Extracts the limit amounts from the rule's `log` entries (each log entry maps comma-separated loan types to a dollar amount). - Returns a flat array of limit amounts, e.g. `[180000, 250000]`. #### 3.4 New Method: `filterBureauSurrogateByFOIR($application, $offerID): array` - For each bureau surrogate limit amount, runs a full EMI calculation to determine the resulting EMI at that loan amount. - Returns an array of `['emi' => ..., 'id' => ..., 'loan_amount' => ...]` entries suitable for comparison. #### 3.5 New Method: `getBureauSurrogateDetails($applicationDetails): array` - Recalculates EMI details for an application that was approved via bureau surrogate logic. - Used by `MerchantController` to display the correct EMI breakdown. #### 3.6 New Method: `handleSolarFormCall($offers): bool` - Extracted from the former inline block inside `saveDecisionInTable` for the solar/no-touch flow. - Calls `checkApprovalStatus`, inserts decision table offers, and handles rejection. #### 3.7 `processResidualEmi()` — Bureau Surrogate Comparison - After computing the highest loan offer from residual EMI, checks if `$this->bureauSurrogateLimits` is populated. - If so, filters the bureau surrogate limits through FOIR (`filterBureauSurrogateByFOIR`). - Takes the **minimum** of the residual-EMI loan amount and the highest bureau surrogate loan amount. - If the bureau surrogate amount is lower, sets `table_of_offers_logic = 'bureau_surrogate_limit'` and stores the bureau surrogate EMI. #### 3.8 `updateApplicationLogic()` — Enhanced - Now accepts optional `$residual_emi` and `$approvedAmount` parameters. - For `residual_emi` logic: updates `emi->residual_emi` and `application->approved_amount`. - For `bureau_surrogate_limit` logic: additionally sets `emi->bureau_surrogate_amount`. #### 3.9 `processOfferChange()` Comment Enhancement - The auto-generated comment now includes a `Changed as per - {tofType}` line indicating which TOF logic was applied (e.g., `residual_emi` or `bureau_surrogate_limit`). #### 3.10 `saveDecisionInTable()` Restructure - Removed the wrapper `handleAdminCall()` (was just proxying to `handleRegularAdminCall`). - Separated solar/no-touch flow into `handleSolarFormCall()`. - Custom EMI flow (power saving / banking) now runs after both admin and solar paths. #### 3.11 `runTOFWithCustomEMI()` — Error Handling - Wrapped in a try-catch block; logs errors to CloudWatch on failure. #### 3.12 `getNextBasedAmountOffer()` — Negative Residual EMI Guard - If `$residual_emi < 0`, sends a Slack alert and returns an empty array instead of proceeding with invalid data. #### 3.13 `addNegativeResidualLog()` Fix - `$policies` is now an associative array; extracts keys via `array_keys()` before using in `whereIn` queries against `JsonBreApprovalStatusLog` and `ApprovalStatusLog`. #### 3.14 Cleanup - Removed dead commented-out code in `getFOIRCalculation()`. - Improved log titles for clarity (`"Auto Decision: BRE execution complete"`, `"Auto Decision: TOF prep"`). ### 4. MerchantController **File:** `app/Http/Controllers/Admin/MerchantController.php` - `$residualFoirLogicApplied` now checks for both `residual_emi` and `bureau_surrogate_limit` in `table_of_offers_logic`. - When `table_of_offers_logic === 'bureau_surrogate_limit'`, calls `GradeController::getBureauSurrogateDetails()` to fetch EMI details for display. ### 5. ConsumerApplicationController **File:** `app/Http/Controllers/Admin/ConsumerApplicationController.php` - Removed the old commented-out `checkCoAppRequire()` method (dead code cleanup). - Added `user_id` to the autoBRE logger call for traceability. ### 6. Grade Approval Policy UI — Bureau Surrogate Modal **File:** `resources/views/admin/grade/edit_v1.blade.php` #### 6.1 Personal Account Count (PAC) Sub-Modal - New modal (`#personalAccountCountModal`) allowing admins to configure multiple Personal Account Count rows per surrogate entry, each with: - **Personal Account Count** (integer) - **Percentage Limit** (decimal %) - Stacked modal CSS fix ensures the PAC modal appears above the existing surrogate modal (`z-index: 1060`). #### 6.2 Surrogate Table Updates - New column: **Personal Account Count Config** — displays a button showing the number of configured PAC rows (e.g., `"2 row(s) configured"`) or `"Configure"` if empty. - When PAC rows are configured, the **Limit**, **Open Vintage**, and **Close Vintage** inputs are **disabled** (PAC config overrides them). - Surrogate row selectors changed from `#surrogate_account_type` / `#surrogate_ownership_type` (ID-based) to `[name="surrogate_account_type[]"]` / `[name="surrogate_ownership_type[]"]` (name-based) for correct multi-row selection. #### 6.3 Data Format - `account_type_count` is now stored as an **array of objects** in the surrogate JSON: ```json [ { "personal_count": 3, "percentage_limit": 50 }, { "personal_count": 5, "percentage_limit": 30 } ] ``` - Legacy formats (single object, bare number) are handled with backward-compatible parsing. ### 7. Consumer Application Edit — Bug Fix **File:** `resources/views/admin/consumer-application/edit_v1.blade.php` - Fixed swapped variables in the offer change handler: - `approvedAmount` now correctly reads from the selected offer's `data-amount`. - `loanAmount` now correctly reads from the `loan_amount` input. - Alert message and field update now reference the correct values. --- ## Flow Diagram ``` BRE Response │ ▼ multiApprovalCheck() │ ├── Extract passed policies (associative: id → object) ├── extractBureauSurrogateLimits() → $this->bureauSurrogateLimits │ ▼ saveDecisionInTable() │ ├── Build decision table, process offers ├── Admin call → handleRegularAdminCall() ├── Solar/No-touch → handleSolarFormCall() │ ▼ processResidualEmi() │ ├── Compute highest loan from residual EMI ├── If bureauSurrogateLimits not empty: │ ├── filterBureauSurrogateByFOIR() → EMI for each limit │ ├── Take MIN(residual loan, bureau surrogate loan) │ └── Set logic = 'bureau_surrogate_limit' if surrogate wins │ ├── updateApplicationLogic() → save EMI + approved amount └── processOfferChange() → update offer + add comment ``` --- ## Configuration Admins configure bureau surrogate rules through **Grade Approval Policy → Bureau Surrogate modal** in the admin panel: 1. Add surrogate rows with Account Type, Ownership Type, and optional PAC config. 2. PAC (Personal Account Count) sub-modal allows multiple count/percentage pairs. 3. When PAC is configured, the flat Limit/Vintage fields are disabled in favor of PAC-driven logic. --- ## Testing Notes - Verify that when BRE returns a `bureau_surrogate` rule with limits lower than the residual EMI amount, the approved amount is capped at the surrogate limit. - Verify the `emi_calculation.table_of_offers_logic` column correctly shows `bureau_surrogate_limit` when the surrogate cap is applied. - Verify the `emi_calculation.bureau_surrogate_amount` column stores the surrogate-capped amount. - Verify the PAC sub-modal in grade edit saves/loads correctly and disables the Limit/Vintage fields. - Verify the merchant offer view (`MerchantController`) displays correct EMI details for bureau-surrogate-approved applications. - Verify the consumer application edit offer-change alert shows correct before/after amounts.