Subversion Repositories SmartDukaan

Rev

Rev 37180 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37167 amit 1
package com.spice.profitmandi.service;
2
 
3
import com.spice.profitmandi.dao.enumuration.dtr.AgendaType;
4
 
5
import java.util.LinkedHashMap;
6
import java.util.Map;
7
 
8
/**
9
 * Pure decision logic for the 5 AUTO agenda rules. No Spring, no DB — the
10
 * nightly sync gathers inputs and this class says per agenda type whether the
11
 * AUTO instance should be OPEN, CLOSED, or left as-is (LEAVE).
12
 *
13
 * LEAVE is load-bearing for LOW_SALES hysteresis: between flagPct and clearPct
14
 * the current state (whatever it is) holds.
15
 */
16
public class AgendaRuleDecider {
17
 
18
    public enum Action {OPEN, CLOSE, LEAVE}
19
 
20
    public static class Decision {
21
        public final Action action;
22
        public final String metric;
23
 
24
        Decision(Action action, String metric) {
25
            this.action = action;
26
            this.metric = metric;
27
        }
28
    }
29
 
30
    /** Per-store inputs, gathered by the sync orchestrator. */
31
    public static class Inputs {
32
        public int fofoId;
33
        public boolean revival;                 // fofo_store.activation_type == REVIVAL
34
        public boolean storeActivatedRecently;  // active_timestamp inside the sales lookback window
35
        public float minInvestment;
36
        public Double shortPercentage;          // null = investment could not be computed
37
        public double baselineAmt;              // fofo sales, days (windowDays..windowDays+baselineDays]
38
        public double currentAmt;               // fofo sales, last windowDays
39
        public Integer lastBillDaysAgo;         // null = never billed
40
        public Integer oldestLoanAgeDays;       // null = no open loan with pending amount
41
        public double pendingLoanAmt;
42
    }
43
 
44
    public static Map<AgendaType, Decision> decide(Inputs in,
45
                                                   Map<String, Double> investParams,
46
                                                   Map<String, Double> salesParams,
47
                                                   Map<String, Double> purchaseParams,
48
                                                   Map<String, Double> creditParams) {
49
        Map<AgendaType, Decision> out = new LinkedHashMap<>();
50
 
51
        // LOW_INVESTMENT — existing platform metric, PJP trigger at shortPct.
52
        if (in.shortPercentage == null || in.minInvestment <= 0) {
53
            out.put(AgendaType.LOW_INVESTMENT, new Decision(Action.LEAVE, null));
54
        } else {
55
            double shortPct = param(investParams, "shortPct", 50);
56
            String metric = "{\"shortPct\":" + Math.round(in.shortPercentage) + "}";
57
            out.put(AgendaType.LOW_INVESTMENT, new Decision(
58
                    in.shortPercentage > shortPct ? Action.OPEN : Action.CLOSE, metric));
59
        }
60
 
61
        // LOW_SALES — hysteresis vs trailing baseline.
62
        double monthlyBaseline = in.baselineAmt / (param(salesParams, "baselineDays", 90) / 30d);
63
        if (in.storeActivatedRecently || monthlyBaseline <= 0) {
64
            out.put(AgendaType.LOW_SALES, new Decision(Action.LEAVE, null));
65
        } else {
66
            double flagPct = param(salesParams, "flagPct", 40);
67
            double clearPct = param(salesParams, "clearPct", 75);
68
            String metric = "{\"current\":" + Math.round(in.currentAmt)
69
                    + ",\"monthlyBaseline\":" + Math.round(monthlyBaseline) + "}";
70
            if (in.currentAmt < flagPct / 100d * monthlyBaseline) {
71
                out.put(AgendaType.LOW_SALES, new Decision(Action.OPEN, metric));
72
            } else if (in.currentAmt > clearPct / 100d * monthlyBaseline) {
73
                out.put(AgendaType.LOW_SALES, new Decision(Action.CLOSE, metric));
74
            } else {
75
                out.put(AgendaType.LOW_SALES, new Decision(Action.LEAVE, metric));
76
            }
77
        }
78
 
79
        // LOW_PURCHASE — days since last billed purchase; REVIVAL stores are
80
        // the Revival agenda's problem, not this one's.
81
        if (in.revival) {
82
            out.put(AgendaType.LOW_PURCHASE, new Decision(Action.CLOSE, "{\"reason\":\"REVIVAL\"}"));
83
        } else {
84
            double gapDays = param(purchaseParams, "gapDays", 15);
85
            boolean flag = in.lastBillDaysAgo == null || in.lastBillDaysAgo >= gapDays;
86
            String metric = "{\"gapDays\":" + (in.lastBillDaysAgo == null ? -1 : in.lastBillDaysAgo) + "}";
87
            out.put(AgendaType.LOW_PURCHASE, new Decision(flag ? Action.OPEN : Action.CLOSE, metric));
88
        }
89
 
90
        // CREDIT_DUES — open loan aged >= loanAgeDays from disbursal (created_on).
91
        double loanAgeDays = param(creditParams, "loanAgeDays", 20);
92
        if (in.oldestLoanAgeDays != null && in.oldestLoanAgeDays >= loanAgeDays) {
93
            out.put(AgendaType.CREDIT_DUES, new Decision(Action.OPEN,
94
                    "{\"oldestLoanDays\":" + in.oldestLoanAgeDays
95
                            + ",\"pending\":" + Math.round(in.pendingLoanAmt) + "}"));
96
        } else {
97
            out.put(AgendaType.CREDIT_DUES, new Decision(Action.CLOSE,
98
                    "{\"oldestLoanDays\":" + (in.oldestLoanAgeDays == null ? 0 : in.oldestLoanAgeDays) + "}"));
99
        }
100
 
101
        // REVIVAL — mirrors the 23:30 activation-type cron.
102
        out.put(AgendaType.REVIVAL, new Decision(
103
                in.revival ? Action.OPEN : Action.CLOSE, "{\"activationType\":\"" + (in.revival ? "REVIVAL" : "ACTIVE") + "\"}"));
104
 
105
        return out;
106
    }
107
 
108
    private static double param(Map<String, Double> params, String key, double fallback) {
109
        if (params == null) return fallback;
110
        Double v = params.get(key);
111
        return v != null ? v : fallback;
112
    }
113
}