Subversion Repositories SmartDukaan

Rev

Rev 37180 | Details | Compare with Previous | 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
 
37181 amit 51
        // NOTE on REVIVAL stores: instances keep their own lifecycle here — the
52
        // rules evaluate on data regardless of activation state. Suppression for
53
        // revival stores happens at the DISPLAY layer (visit agenda + checkout
54
        // remarks show only REVIVAL, CREDIT_DUES and MANUAL instances — see
55
        // AgendaInstanceServiceImpl.filterVisibleForVisit).
37180 amit 56
 
37167 amit 57
        // LOW_INVESTMENT — existing platform metric, PJP trigger at shortPct.
37181 amit 58
        if (in.shortPercentage == null || in.minInvestment <= 0) {
37167 amit 59
            out.put(AgendaType.LOW_INVESTMENT, new Decision(Action.LEAVE, null));
60
        } else {
61
            double shortPct = param(investParams, "shortPct", 50);
62
            String metric = "{\"shortPct\":" + Math.round(in.shortPercentage) + "}";
63
            out.put(AgendaType.LOW_INVESTMENT, new Decision(
64
                    in.shortPercentage > shortPct ? Action.OPEN : Action.CLOSE, metric));
65
        }
66
 
67
        // LOW_SALES — hysteresis vs trailing baseline.
68
        double monthlyBaseline = in.baselineAmt / (param(salesParams, "baselineDays", 90) / 30d);
37181 amit 69
        if (in.storeActivatedRecently || monthlyBaseline <= 0) {
37167 amit 70
            out.put(AgendaType.LOW_SALES, new Decision(Action.LEAVE, null));
71
        } else {
72
            double flagPct = param(salesParams, "flagPct", 40);
73
            double clearPct = param(salesParams, "clearPct", 75);
74
            String metric = "{\"current\":" + Math.round(in.currentAmt)
75
                    + ",\"monthlyBaseline\":" + Math.round(monthlyBaseline) + "}";
76
            if (in.currentAmt < flagPct / 100d * monthlyBaseline) {
77
                out.put(AgendaType.LOW_SALES, new Decision(Action.OPEN, metric));
78
            } else if (in.currentAmt > clearPct / 100d * monthlyBaseline) {
79
                out.put(AgendaType.LOW_SALES, new Decision(Action.CLOSE, metric));
80
            } else {
81
                out.put(AgendaType.LOW_SALES, new Decision(Action.LEAVE, metric));
82
            }
83
        }
84
 
37180 amit 85
        // LOW_PURCHASE — days since last billed purchase.
37181 amit 86
        {
37167 amit 87
            double gapDays = param(purchaseParams, "gapDays", 15);
88
            boolean flag = in.lastBillDaysAgo == null || in.lastBillDaysAgo >= gapDays;
89
            String metric = "{\"gapDays\":" + (in.lastBillDaysAgo == null ? -1 : in.lastBillDaysAgo) + "}";
90
            out.put(AgendaType.LOW_PURCHASE, new Decision(flag ? Action.OPEN : Action.CLOSE, metric));
91
        }
92
 
93
        // CREDIT_DUES — open loan aged >= loanAgeDays from disbursal (created_on).
94
        double loanAgeDays = param(creditParams, "loanAgeDays", 20);
95
        if (in.oldestLoanAgeDays != null && in.oldestLoanAgeDays >= loanAgeDays) {
96
            out.put(AgendaType.CREDIT_DUES, new Decision(Action.OPEN,
97
                    "{\"oldestLoanDays\":" + in.oldestLoanAgeDays
98
                            + ",\"pending\":" + Math.round(in.pendingLoanAmt) + "}"));
99
        } else {
100
            out.put(AgendaType.CREDIT_DUES, new Decision(Action.CLOSE,
101
                    "{\"oldestLoanDays\":" + (in.oldestLoanAgeDays == null ? 0 : in.oldestLoanAgeDays) + "}"));
102
        }
103
 
104
        // REVIVAL — mirrors the 23:30 activation-type cron.
105
        out.put(AgendaType.REVIVAL, new Decision(
106
                in.revival ? Action.OPEN : Action.CLOSE, "{\"activationType\":\"" + (in.revival ? "REVIVAL" : "ACTIVE") + "\"}"));
107
 
108
        return out;
109
    }
110
 
111
    private static double param(Map<String, Double> params, String key, double fallback) {
112
        if (params == null) return fallback;
113
        Double v = params.get(key);
114
        return v != null ? v : fallback;
115
    }
116
}