Rev 37180 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
package com.spice.profitmandi.service;import com.spice.profitmandi.dao.enumuration.dtr.AgendaType;import java.util.LinkedHashMap;import java.util.Map;/*** Pure decision logic for the 5 AUTO agenda rules. No Spring, no DB — the* nightly sync gathers inputs and this class says per agenda type whether the* AUTO instance should be OPEN, CLOSED, or left as-is (LEAVE).** LEAVE is load-bearing for LOW_SALES hysteresis: between flagPct and clearPct* the current state (whatever it is) holds.*/public class AgendaRuleDecider {public enum Action {OPEN, CLOSE, LEAVE}public static class Decision {public final Action action;public final String metric;Decision(Action action, String metric) {this.action = action;this.metric = metric;}}/** Per-store inputs, gathered by the sync orchestrator. */public static class Inputs {public int fofoId;public boolean revival; // fofo_store.activation_type == REVIVALpublic boolean storeActivatedRecently; // active_timestamp inside the sales lookback windowpublic float minInvestment;public Double shortPercentage; // null = investment could not be computedpublic double baselineAmt; // fofo sales, days (windowDays..windowDays+baselineDays]public double currentAmt; // fofo sales, last windowDayspublic Integer lastBillDaysAgo; // null = never billedpublic Integer oldestLoanAgeDays; // null = no open loan with pending amountpublic double pendingLoanAmt;}public static Map<AgendaType, Decision> decide(Inputs in,Map<String, Double> investParams,Map<String, Double> salesParams,Map<String, Double> purchaseParams,Map<String, Double> creditParams) {Map<AgendaType, Decision> out = new LinkedHashMap<>();// LOW_INVESTMENT — existing platform metric, PJP trigger at shortPct.if (in.shortPercentage == null || in.minInvestment <= 0) {out.put(AgendaType.LOW_INVESTMENT, new Decision(Action.LEAVE, null));} else {double shortPct = param(investParams, "shortPct", 50);String metric = "{\"shortPct\":" + Math.round(in.shortPercentage) + "}";out.put(AgendaType.LOW_INVESTMENT, new Decision(in.shortPercentage > shortPct ? Action.OPEN : Action.CLOSE, metric));}// LOW_SALES — hysteresis vs trailing baseline.double monthlyBaseline = in.baselineAmt / (param(salesParams, "baselineDays", 90) / 30d);if (in.storeActivatedRecently || monthlyBaseline <= 0) {out.put(AgendaType.LOW_SALES, new Decision(Action.LEAVE, null));} else {double flagPct = param(salesParams, "flagPct", 40);double clearPct = param(salesParams, "clearPct", 75);String metric = "{\"current\":" + Math.round(in.currentAmt)+ ",\"monthlyBaseline\":" + Math.round(monthlyBaseline) + "}";if (in.currentAmt < flagPct / 100d * monthlyBaseline) {out.put(AgendaType.LOW_SALES, new Decision(Action.OPEN, metric));} else if (in.currentAmt > clearPct / 100d * monthlyBaseline) {out.put(AgendaType.LOW_SALES, new Decision(Action.CLOSE, metric));} else {out.put(AgendaType.LOW_SALES, new Decision(Action.LEAVE, metric));}}// LOW_PURCHASE — days since last billed purchase; REVIVAL stores are// the Revival agenda's problem, not this one's.if (in.revival) {out.put(AgendaType.LOW_PURCHASE, new Decision(Action.CLOSE, "{\"reason\":\"REVIVAL\"}"));} else {double gapDays = param(purchaseParams, "gapDays", 15);boolean flag = in.lastBillDaysAgo == null || in.lastBillDaysAgo >= gapDays;String metric = "{\"gapDays\":" + (in.lastBillDaysAgo == null ? -1 : in.lastBillDaysAgo) + "}";out.put(AgendaType.LOW_PURCHASE, new Decision(flag ? Action.OPEN : Action.CLOSE, metric));}// CREDIT_DUES — open loan aged >= loanAgeDays from disbursal (created_on).double loanAgeDays = param(creditParams, "loanAgeDays", 20);if (in.oldestLoanAgeDays != null && in.oldestLoanAgeDays >= loanAgeDays) {out.put(AgendaType.CREDIT_DUES, new Decision(Action.OPEN,"{\"oldestLoanDays\":" + in.oldestLoanAgeDays+ ",\"pending\":" + Math.round(in.pendingLoanAmt) + "}"));} else {out.put(AgendaType.CREDIT_DUES, new Decision(Action.CLOSE,"{\"oldestLoanDays\":" + (in.oldestLoanAgeDays == null ? 0 : in.oldestLoanAgeDays) + "}"));}// REVIVAL — mirrors the 23:30 activation-type cron.out.put(AgendaType.REVIVAL, new Decision(in.revival ? Action.OPEN : Action.CLOSE, "{\"activationType\":\"" + (in.revival ? "REVIVAL" : "ACTIVE") + "\"}"));return out;}private static double param(Map<String, Double> params, String key, double fallback) {if (params == null) return fallback;Double v = params.get(key);return v != null ? v : fallback;}}