Subversion Repositories SmartDukaan

Rev

Rev 37167 | Go to most recent revision | 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
 
37180 amit 51
        // REVIVAL store: the Revival agenda owns the visit — every data-driven
52
        // agenda EXCEPT loan overdue (CREDIT_DUES) is suppressed (AUTO instances
53
        // close; MANUAL ones are never touched by the cron).
54
        Decision revivalSuppressed = new Decision(Action.CLOSE, "{\"reason\":\"REVIVAL\"}");
55
 
37167 amit 56
        // LOW_INVESTMENT — existing platform metric, PJP trigger at shortPct.
37180 amit 57
        if (in.revival) {
58
            out.put(AgendaType.LOW_INVESTMENT, revivalSuppressed);
59
        } else if (in.shortPercentage == null || in.minInvestment <= 0) {
37167 amit 60
            out.put(AgendaType.LOW_INVESTMENT, new Decision(Action.LEAVE, null));
61
        } else {
62
            double shortPct = param(investParams, "shortPct", 50);
63
            String metric = "{\"shortPct\":" + Math.round(in.shortPercentage) + "}";
64
            out.put(AgendaType.LOW_INVESTMENT, new Decision(
65
                    in.shortPercentage > shortPct ? Action.OPEN : Action.CLOSE, metric));
66
        }
67
 
68
        // LOW_SALES — hysteresis vs trailing baseline.
69
        double monthlyBaseline = in.baselineAmt / (param(salesParams, "baselineDays", 90) / 30d);
37180 amit 70
        if (in.revival) {
71
            out.put(AgendaType.LOW_SALES, revivalSuppressed);
72
        } else if (in.storeActivatedRecently || monthlyBaseline <= 0) {
37167 amit 73
            out.put(AgendaType.LOW_SALES, new Decision(Action.LEAVE, null));
74
        } else {
75
            double flagPct = param(salesParams, "flagPct", 40);
76
            double clearPct = param(salesParams, "clearPct", 75);
77
            String metric = "{\"current\":" + Math.round(in.currentAmt)
78
                    + ",\"monthlyBaseline\":" + Math.round(monthlyBaseline) + "}";
79
            if (in.currentAmt < flagPct / 100d * monthlyBaseline) {
80
                out.put(AgendaType.LOW_SALES, new Decision(Action.OPEN, metric));
81
            } else if (in.currentAmt > clearPct / 100d * monthlyBaseline) {
82
                out.put(AgendaType.LOW_SALES, new Decision(Action.CLOSE, metric));
83
            } else {
84
                out.put(AgendaType.LOW_SALES, new Decision(Action.LEAVE, metric));
85
            }
86
        }
87
 
37180 amit 88
        // LOW_PURCHASE — days since last billed purchase.
37167 amit 89
        if (in.revival) {
37180 amit 90
            out.put(AgendaType.LOW_PURCHASE, revivalSuppressed);
37167 amit 91
        } else {
92
            double gapDays = param(purchaseParams, "gapDays", 15);
93
            boolean flag = in.lastBillDaysAgo == null || in.lastBillDaysAgo >= gapDays;
94
            String metric = "{\"gapDays\":" + (in.lastBillDaysAgo == null ? -1 : in.lastBillDaysAgo) + "}";
95
            out.put(AgendaType.LOW_PURCHASE, new Decision(flag ? Action.OPEN : Action.CLOSE, metric));
96
        }
97
 
98
        // CREDIT_DUES — open loan aged >= loanAgeDays from disbursal (created_on).
99
        double loanAgeDays = param(creditParams, "loanAgeDays", 20);
100
        if (in.oldestLoanAgeDays != null && in.oldestLoanAgeDays >= loanAgeDays) {
101
            out.put(AgendaType.CREDIT_DUES, new Decision(Action.OPEN,
102
                    "{\"oldestLoanDays\":" + in.oldestLoanAgeDays
103
                            + ",\"pending\":" + Math.round(in.pendingLoanAmt) + "}"));
104
        } else {
105
            out.put(AgendaType.CREDIT_DUES, new Decision(Action.CLOSE,
106
                    "{\"oldestLoanDays\":" + (in.oldestLoanAgeDays == null ? 0 : in.oldestLoanAgeDays) + "}"));
107
        }
108
 
109
        // REVIVAL — mirrors the 23:30 activation-type cron.
110
        out.put(AgendaType.REVIVAL, new Decision(
111
                in.revival ? Action.OPEN : Action.CLOSE, "{\"activationType\":\"" + (in.revival ? "REVIVAL" : "ACTIVE") + "\"}"));
112
 
113
        return out;
114
    }
115
 
116
    private static double param(Map<String, Double> params, String key, double fallback) {
117
        if (params == null) return fallback;
118
        Double v = params.get(key);
119
        return v != null ? v : fallback;
120
    }
121
}