modelswrkpi 8.0.5__py3-none-any.whl → 8.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of modelswrkpi might be problematic. Click here for more details.

@@ -0,0 +1,6 @@
1
+ from models.bro_clicks.load_balancer.v1 import LoadBalancer
2
+ from models.bro_clicks.load_balancer.v2 import LoadBalancerV2
3
+ from models.bro_clicks.load_balancer.v3 import LoadBalancerV3
4
+ from models.bro_clicks.load_balancer.v4 import LoadBalancerV4
5
+ from models.bro_clicks.load_balancer.v5 import LoadBalancerV5
6
+ from models.bro_clicks.load_balancer.v6 import LoadBalancerV6
@@ -0,0 +1,16 @@
1
+ from distutils.core import setup
2
+ from distutils.extension import Extension
3
+ from Cython.Distutils import build_ext
4
+ ext_modules = [
5
+ Extension("v1", ["v1.py"], extra_compile_args=["-g0"]),
6
+ Extension("v2", ["v2.py"], extra_compile_args=["-g0"]),
7
+ Extension("v3", ["v3.py"], extra_compile_args=["-g0"]),
8
+ Extension("v4", ["v4.py"], extra_compile_args=["-g0"]),
9
+ Extension("v5", ["v5.py"], extra_compile_args=["-g0"]),
10
+ Extension("v6", ["v6.py"], extra_compile_args=["-g0"]),
11
+ ]
12
+ setup(
13
+ name='load_balancer',
14
+ cmdclass={'build_ext': build_ext},
15
+ ext_modules=ext_modules
16
+ )
@@ -0,0 +1,176 @@
1
+ import random
2
+ from models.db import Db, pd
3
+ from calendar import monthrange
4
+ import datetime as dt
5
+ from models import config
6
+
7
+
8
+ class LoadBalancer(Db):
9
+ _max_decline_attempts = 5
10
+ def __init__(self, db, db_p, account_id='54407332', **kw):
11
+ Db.__init__(self, db, f"bro_clicks", 'load_balancer_2')
12
+ self.set_constraint('load_balancer_2_pk', ['date', 'crm_id', 'gateway_id', 'router_id'])
13
+ self.db_p = db_p
14
+ self._account_id = account_id
15
+
16
+ @staticmethod
17
+ def set_max_decline_attempts(attempts):
18
+ LoadBalancer._max_decline_attempts = int(attempts)
19
+ print(f'max decline attempts set to {attempts}', flush=True)
20
+
21
+ @staticmethod
22
+ def now():
23
+ return dt.datetime.now() - dt.timedelta(hours=config.timeOffset)
24
+
25
+ @staticmethod
26
+ def today():
27
+ return LoadBalancer.now().date()
28
+
29
+ @staticmethod
30
+ def get_first_om():
31
+ now = LoadBalancer.now()
32
+ return dt.datetime(year=now.year, month=now.month, day=1)
33
+
34
+ @staticmethod
35
+ def get_last_dom(now=False):
36
+ now = now if now else LoadBalancer.now()
37
+ weekday_of, last_day = monthrange(now.year, now.month)
38
+ return last_day
39
+
40
+ @staticmethod
41
+ def get_drim():
42
+ now = LoadBalancer.now()
43
+ return LoadBalancer.get_last_dom() - now.day + 1
44
+
45
+ def get_processing_for_month(self, crm_id):
46
+ qry = f"""
47
+ select b.gateway_id, count(a.order_id)::int initial_count_mtd from augmented_data.order_cycles a
48
+ inner join crm_global.orders b on a.order_id = b.order_id and a.crm_id = b.crm_id and a.crm_id = '{crm_id}'
49
+ where a.time_stamp > '{self.get_first_om()}' and a.time_stamp < '{self.today()}'::timestamp
50
+ and a.bc_inferred = 0
51
+ and a.decline_reason is null
52
+ and b.is_test_cc::int <> '1'
53
+ group by b.gateway_id
54
+ """
55
+
56
+ # print(qry)
57
+ return pd.read_sql(qry, self.db_p.engine).fillna(0)
58
+
59
+ def init_date(self, date, crm_id, reset_cap_count=True):
60
+ sk = f'ui_{self._account_id}_clients'
61
+ real_cap_space = self.get_processing_for_month(crm_id) if reset_cap_count else None
62
+
63
+ qry = f"""
64
+ SELECT '{date}'::date as date, b.crm_id, a.mid_id, b.gateway_id, b.step, a.processor,
65
+ coalesce(e.approved, 0) approved, coalesce(e.approved,0) initial_count, c.dly_initial_cap, b.minimum_price, coalesce(e.declined, 0) declined,
66
+ d.approval_rate, c.dly_min_approval_rate, array_to_string(c.pr_exclude_cc_types, ',') exclude_cc_types ,
67
+ c.date_added, c.enable_tds, array_to_string(c.tds_exclude_cc_types, ',') tds_exclude_cc_types, c.enabled, c.enable_initials, c.monthly_initial_cap, c.priority, c.router_id, d.router_id as cur_router_id,
68
+ d.soft_cap_alerted, d.initial_count_mtd as prev_mtd
69
+
70
+ FROM {sk}.mids a
71
+ LEFT JOIN {sk}.steps b on b.mid_id = a.mid_id
72
+ LEFT JOIN {sk}.gateway_settings c on c.gateway_id = b.gateway_id and c.crm_id = b.crm_id
73
+ LEFT JOIN {self.schema}.{self.table} d on c.gateway_id =d.gateway_id and c.crm_id = d.crm_id and b.step = d.step and '{date}'::date =d.date
74
+ LEFT JOIN (select crm_id, gateway_id, coalesce(sum(declined), 0) declined, coalesce(sum(approved), 0) approved
75
+ from {self.schema}.conversions where coalesce(test, 0) <> 1 and time_stamp::date = '{date}'::date group by crm_id, gateway_id
76
+ ) e on e.gateway_id =c.gateway_id and e.crm_id=c.crm_id
77
+ where (b.close_date is null or b.close_date >'{self.today()}')
78
+ and b.crm_id = '{crm_id}'
79
+ and b.gateway_id is not null
80
+ and a.processor not ilike '%%virtual%%'
81
+ and b.gateway_id::int <> 1
82
+ and a.processor != 'FlexCharge'
83
+
84
+ """
85
+
86
+ try:
87
+
88
+ # if crm_id != 'crm_ll_2':
89
+ #
90
+
91
+ # print(qry)
92
+ # print('break')
93
+ up = pd.read_sql(qry, self.engine)
94
+
95
+ up = up.sort_values('step').drop_duplicates(['gateway_id', 'cur_router_id'], keep='first')
96
+ up = up.loc[~up.router_id.isna()]
97
+ up = up.explode('router_id')
98
+
99
+ # delete changes to routers
100
+ del_gt_msk = (up.router_id != up.cur_router_id) & (
101
+ up.gateway_id.isin(up.loc[~up.cur_router_id.isna()].gateway_id.unique()))
102
+ del_gtys = up.loc[del_gt_msk].gateway_id.tolist()
103
+
104
+ up = up.loc[(~up.gateway_id.isin(del_gtys)) | (~up.cur_router_id.isna())]
105
+
106
+ # delete changes to routers
107
+ del_gt_msk = (up.router_id != up.cur_router_id)
108
+ del_gtys = up.loc[del_gt_msk].gateway_id.tolist()
109
+ self.engine.execute(
110
+ f"delete from {self.schema}.{self.table} where gateway_id::int = ANY(ARRAY{del_gtys}::int[]) and crm_id='{crm_id}'")
111
+ up = up.drop(columns='cur_router_id')
112
+ except Exception as e:
113
+ raise e
114
+ if reset_cap_count:
115
+ try:
116
+ up = up.merge(real_cap_space, on=['gateway_id'], how='left')
117
+ up.initial_count_mtd = up.initial_count_mtd.fillna(0)
118
+ up.initial_count_mtd += up.initial_count
119
+
120
+ except:
121
+ up['initial_count_mtd'] = up.prev_mtd.fillna(0)
122
+ up.initial_count_mtd = up.initial_count_mtd.fillna(0)
123
+
124
+ drim = float(self.get_drim())
125
+ up.dly_initial_cap = pd.np.floor((up.monthly_initial_cap - up.initial_count_mtd) / drim)
126
+ up.loc[up.dly_initial_cap < 0, 'dly_initial_cap'] = 0
127
+
128
+ up.dly_initial_cap = up.dly_initial_cap.fillna(11)
129
+ up.dly_min_approval_rate = up.dly_min_approval_rate.fillna(30)
130
+ up.declined = up.declined.fillna(0)
131
+ up.approval_rate = up.approval_rate.fillna(0)
132
+ up.soft_cap_alerted = up.soft_cap_alerted.fillna(False)
133
+ up.drop('prev_mtd', axis=1, errors='ignore', inplace=True)
134
+ up = up.drop_duplicates(['gateway_id', 'router_id'])
135
+ # self.engine.execute(f'truncate {self.schema}.{self.table}')
136
+ self.upsert(up.dropna())
137
+
138
+ def _increment_conversion(self, date, gateway_id, crm_id, approved, recurs_attempt=0, **kwargs):
139
+ inc_p = '(initial_count +1)'
140
+ m_inc_p = '(initial_count_mtd +1)'
141
+ dnc_p = '(declined + 1)'
142
+ inc = 'initial_count'
143
+ m_inc = 'initial_count_mtd'
144
+ dnc = 'declined'
145
+ try:
146
+ qry = f"""
147
+ UPDATE {self.schema}.{self.table}
148
+ set {f"{inc} ={inc_p}, approval_rate = ({inc_p}::numeric / ({dnc}+{inc_p}::numeric))*100, {m_inc} = {m_inc_p}" if approved
149
+ else f"{dnc} ={dnc_p}, approval_rate = case when {inc}>0 then ({inc} / ({dnc_p}+{inc}))*100 else 0 end "
150
+ }
151
+ where crm_id = '{crm_id}' and date = '{date}'::date and gateway_id='{gateway_id}'
152
+ returning gateway_id
153
+ """
154
+
155
+ if self.engine.execute(qry).scalar() is None and not recurs_attempt:
156
+ self.init_date(date, crm_id)
157
+ if not recurs_attempt:
158
+ return self._increment_conversion(date, gateway_id, crm_id, approved, recurs_attempt + 1)
159
+
160
+ except Exception as e:
161
+ print(e)
162
+ return False
163
+ return True
164
+
165
+ def increment_conversion(self, date, gateway_id, crm_id, approved, **kwargs):
166
+ return self._increment_conversion(date, gateway_id, crm_id, approved, recurs_attempt=0, **kwargs)
167
+
168
+ def set_soft_cap_alerted(self, crm_id):
169
+ self.engine.execute(
170
+ f"""Update {self.schema}.{self.table} set soft_cap_alerted=true where crm_id= '{crm_id}'""")
171
+
172
+ def disable(self, crm_id, gateway_id):
173
+ self.engine.execute(
174
+ f"""Update {self.schema}.{self.table} set enable_initials=false where crm_id= '{crm_id}' and gateway_id = '{int(gateway_id)}'""")
175
+ self.db_p.engine.execute(
176
+ f"update ui_54407332_clients.gateway_settings set enable_initials=false where crm_id='{crm_id}' and gateway_id='{gateway_id}'")
@@ -0,0 +1,128 @@
1
+ from models.bro_clicks.load_balancer.v1 import LoadBalancer
2
+ import pandas as pd
3
+
4
+
5
+ class LoadBalancerV2(LoadBalancer):
6
+ def __init__(self, db, db_p, account_id='54407332', alert_call_back=False, **kwargs):
7
+ LoadBalancer.__init__(self, db, db_p, account_id=account_id)
8
+ self.alert_cb = alert_call_back
9
+
10
+ self.sort_map = {
11
+ # No optimization
12
+ 4: {'by': ['priority', 'date_added', 'fill_pct', 'approval_rate', 'initial_count'],
13
+ 'ascending': [False, False, True, False, True]}
14
+ }
15
+
16
+ def gty_qry(self, crm_id, date, step, processor, cc_type=False, decs='', proc_excl=[], is_tds=None, is_decline_salvage=False, **kw):
17
+ p_ex = ''
18
+ if proc_excl and len(proc_excl) and not processor:
19
+ p_ex = f"and a.processor not ilike all(ARRAY{[f'%%{p}%%' for p in proc_excl]}::text[])"
20
+
21
+ return f""" --LEFT HERE NEED TO GET MCC!
22
+ select a.gateway_id::int, a.fill_pct,a.dly_initial_cap,priority,initial_count, a.approval_rate, a.date_added, a.processor, a.mid_id, a.monthly_initial_cap, a.soft_cap_alerted, initial_count_mtd,b.mcc from {self.schema}.{self.table} a
23
+ inner join (select crm_id, gateway_id, mcc from ui_54407332_clients.steps ) b on b.crm_id = a.crm_id and b.gateway_id=a.gateway_id
24
+ {"inner join (select crm_id, gateway_id where enable_decline_salvage) ds on ds.crm_id = a.crm_id and ds.gateway_id::int = a.gateway_id::int" if is_decline_salvage else "" }
25
+ inner join processing.cap c on a.mid_id = c.mid_id and a.step=c.step and a.processor=c.processor and c.monthly_available > 200
26
+ {f"left join processing.cap_cc_type d on a.mid_id = d.mid_id and a.step= d.step and a.processor = d.processor and d.cc_type = '{cc_type}' " if cc_type else ''}
27
+
28
+ where date = '{date}'::date and a.crm_id = '{crm_id}' and router_id = '{step if step in [1, 11] else 2}' and enabled and enable_initials
29
+ {f"and a.processor = '{processor}'" if processor else ""}
30
+ {f"and (exclude_cc_types is null or exclude_cc_types::text not ilike '%%{cc_type.lower()}%%')" if cc_type else ''}
31
+ and (approval_rate > dly_min_approval_rate or(declined+initial_count<110))
32
+ {'and (d.available_tc is null or d.available_tc >50)' if cc_type else ''}
33
+ {decs}
34
+ {p_ex}
35
+ {f"and enable_tds = {bool(is_tds)}" if is_tds else ""}
36
+ {f"and (tds_exclude_cc_types is null or tds_exclude_cc_types not ilike '%%{cc_type}%%')" if cc_type and is_tds else ""}
37
+ --and fill_pct < 1
38
+ --order by date_added desc, approval_rate desc, fill_pct asc limit 1
39
+ order by priority desc, date_added desc, fill_pct, approval_rate desc, initial_count
40
+
41
+ """
42
+
43
+ def exclude_list(self, crm_id, step, click_id, alias=''):
44
+ decs = pd.read_sql(f"""SELECT gateway_id, processor, approved from {self.schema}.conversions
45
+ where crm_id = '{crm_id}' and click_id = '{click_id}' and decline_reason not ilike 'prepaid%%' """,
46
+ self.engine)
47
+
48
+ whd = ""
49
+ if len(decs):
50
+ decs.gateway_id = decs.gateway_id.fillna(-1)
51
+ decs.processor = decs.processor.fillna('')
52
+ processors = decs.loc[decs.approved == 0].processor.astype(str).tolist()
53
+ if len(processors) > self._max_decline_attempts:
54
+ raise Exception('declined due to too many attempts')
55
+ p_break = []
56
+ for p in processors:
57
+ p_break.extend(p.split(' '))
58
+
59
+ whd = f"""and {alias}gateway_id != all(ARRAY{decs.gateway_id.astype(int).tolist()})
60
+ {f"and {alias}processor not ilike all(ARRAY{[f'%%{p}%%' for p in p_break]})" if len(p_break) else ""}"""
61
+
62
+ return whd
63
+
64
+ def next_gateway(self, crm_id, date, step, click_id='', processor=False, cc_type=None, recurse=0, decs=False,
65
+ proc_excl=[], is_tds=None,
66
+ **kwargs):
67
+ try:
68
+ decs = self.exclude_list(crm_id, step, click_id, alias='a.') if not decs else decs
69
+ except Exception as e:
70
+ return str(e)
71
+
72
+ qry = self.gty_qry(crm_id, date, step, processor, cc_type, decs, proc_excl, is_tds=is_tds, **kwargs)
73
+ # print(qry)
74
+ res = None
75
+ try:
76
+ res = pd.read_sql(qry, self.engine)
77
+ except Exception as e:
78
+ print(str(e))
79
+ if res is None or not len(res):
80
+ if not decs:
81
+ if not recurse and is_tds is not None:
82
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
83
+ is_tds=not is_tds)
84
+ elif recurse == 1:
85
+ self.init_date(date, crm_id)
86
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
87
+ is_tds=is_tds)
88
+ elif recurse == 2 and is_tds is not None:
89
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
90
+ is_tds=None)
91
+
92
+ return 'out of processing'
93
+ else:
94
+ return 'declined due to too many attempts'
95
+ r = res.loc[res.fill_pct < 1]
96
+
97
+ # HARD CAP
98
+ if not len(r):
99
+ res = res.sort_values(['dly_initial_cap'], ascending=False).sort_values(['fill_pct'])
100
+
101
+ def _get_aft_sc():
102
+ nonlocal res
103
+ if not len(res):
104
+ return 'out of processing'
105
+ r2 = res.to_dict(orient='records')[0]
106
+ if r2['initial_count_mtd'] >= r2['monthly_initial_cap']:
107
+ self.alert_cb('hard_cap_alert', crm_id=crm_id, gateway_id=r2['gateway_id'])
108
+ self.disable(crm_id=crm_id, gateway_id=r2['gateway_id'])
109
+ res = res.loc[res.gateway_id != r2['gateway_id']]
110
+ return _get_aft_sc()
111
+ r2['is_tds'] = is_tds
112
+ return r2
113
+
114
+ if ~res.soft_cap_alerted.any():
115
+ cnt = self.engine.execute(
116
+ f"""select count(*) from {self.schema}.{self.table} where date = '{date}'::date and crm_id = '{crm_id}' and router_id = '{step if step == 1 else 2}' and enabled and enable_initials and fill_pct<1""").scalar()
117
+ if cnt == 0 or cnt is None:
118
+ self.alert_cb('soft_cap_alert', crm_id=crm_id)
119
+ self.set_soft_cap_alerted(crm_id)
120
+ return _get_aft_sc()
121
+ r = r.to_dict(orient='records')[0]
122
+ r['is_tds'] = is_tds
123
+ return r
124
+
125
+ def increment_conversion(self, date, gateway_id, crm_id, approved, **kwargs):
126
+
127
+ return self._increment_conversion(date, gateway_id, crm_id, approved, recurs_attempt=0, **kwargs)
128
+
@@ -0,0 +1,378 @@
1
+ import random
2
+ import pandas as pd
3
+ from models.bro_clicks.initial_routes import InitialRoutes
4
+ import datetime as dt
5
+ from threading import Thread, Lock
6
+ from copy import deepcopy
7
+ from models.bro_clicks.load_balancer.v2 import LoadBalancerV2
8
+
9
+
10
+ class LoadBalancerV3(LoadBalancerV2):
11
+ routes = None
12
+ _lock_static = Lock()
13
+ _lock_static_route_get = Lock()
14
+ _lock_model_replace = Lock()
15
+ _last_updated = False
16
+ _max_stale_seconds = random.randint(600, 1200)
17
+ _route_schema = 'initial_route'
18
+
19
+ def __init__(self, db, db_p, account_id='54407332', alert_call_back=False,
20
+ min_sample_count=500, optimise_pct=0.1,
21
+ randomise_pct=0.1, opt_type=4,
22
+ route_methods=['bank_conversion',
23
+ 'iin_conversion',
24
+ 'cc_type_conversion',
25
+ 'cc_type_mcc_conversion',
26
+ 'cc_type_cc_level_conversion',
27
+ ],
28
+ rewrite_route='initial_route' ,
29
+ iin_schema='bro_clicks',
30
+
31
+ **kwargs):
32
+ is_new_route = False
33
+ if rewrite_route:
34
+ is_new_route = LoadBalancerV3._route_schema != rewrite_route
35
+ LoadBalancerV3._route_schema = rewrite_route
36
+ self._iin_schema = iin_schema
37
+ self._model_class = InitialRoutes
38
+ LoadBalancerV2.__init__(self, db, db_p, account_id=account_id, alert_call_back=alert_call_back, **kwargs)
39
+ self._opt_arr = self._rand_arr(100, opt_type, {'key': 1, 'pct': randomise_pct}, {'key': 2, 'pct': optimise_pct})
40
+ self._route_methods = route_methods
41
+ self._opt_val = self.get_opt_type()
42
+ print('opt val', self._opt_val, 'opt_type', opt_type)
43
+ self._min_sample_count = min_sample_count
44
+ self._t_get_bin = False
45
+ self.iin_info = None
46
+ self.is_iin_data = False
47
+ self._t_get_route = False
48
+ self.sort_map = {
49
+ # Random
50
+ 1: {'by': ['fill_pct', 'initial_count'], 'ascending': [True, True]},
51
+ # Pure conversion rate
52
+ 2: {'by': ['conversion_rate', 'initial_count'], 'ascending': [False, True]},
53
+ # Hybrid optimization
54
+ 3: {'by': ['priority', 'conversion_rate', 'date_added', 'fill_pct', 'approval_rate', 'initial_count'],
55
+ 'ascending': [False, False, False, True, False, True]},
56
+ # No optimization
57
+ 4: {'by': ['priority', 'date_added', 'fill_pct', 'approval_rate', 'initial_count'],
58
+ 'ascending': [False, False, True, False, True]}
59
+ }
60
+ self._join_on_del = False
61
+ self.version = 3
62
+ self.init_static(db_p, route_methods=route_methods, is_rewrite=is_new_route)
63
+
64
+ def __del__(self):
65
+ self._joiner(self._t_get_bin)
66
+ # self._join_lb()
67
+
68
+ @staticmethod
69
+ def _rand_arr(length, default_val, *settings):
70
+ a = []
71
+ for s in settings:
72
+ a += [s['key'] for i in range(int(length * s['pct']))]
73
+ a += [default_val for i in range(length - len(a))]
74
+ return a
75
+
76
+ @staticmethod
77
+ def _rand_val(arr):
78
+ try:
79
+ return arr[random.randint(0, 100)]
80
+ except Exception as e:
81
+ print(str(e))
82
+ return 1
83
+
84
+ def get_opt_type(self):
85
+ return self._rand_val(self._opt_arr)
86
+
87
+ @staticmethod
88
+ def _joiner(*threads):
89
+ for t in threads:
90
+ try:
91
+ t.join()
92
+ except:
93
+ pass
94
+
95
+ def set_bin_info(self, cc_first_6):
96
+ def _exec():
97
+ nonlocal cc_first_6
98
+ self.iin_info = pd.read_sql(f"select * from {self._iin_schema}.iin_data where bin='{cc_first_6}'",
99
+ self.db_p.engine).astype(str).applymap(str.lower).replace({'none': None})
100
+ if len(self.iin_info):
101
+ self.is_iin_data = True
102
+ self.iin_info = self.iin_info.to_dict(orient='records')[0]
103
+ if self.iin_info['bank_map'] is not None:
104
+ self.iin_info['bank'] = self.iin_info['bank_map']
105
+ if 'level' not in self.iin_info:
106
+ self.iin_info['level'] = None
107
+ else:
108
+ self.iin_info = {}
109
+ self.iin_info['cc_first_6'] = cc_first_6
110
+
111
+ self._t_get_bin = Thread(target=_exec)
112
+ self._t_get_bin.start()
113
+
114
+ @staticmethod
115
+ def get_auto_routes(db, route_methods=[
116
+ 'bank_conversion',
117
+ 'iin_conversion',
118
+ 'cc_type_conversion',
119
+ 'cc_type_mcc_conversion',
120
+ 'cc_type_cc_level_conversion'
121
+ ]):
122
+ threads = []
123
+ print('LBV3 get auto routes')
124
+ if LoadBalancerV3._lock_static_route_get.acquire(timeout=0.001):
125
+ rts = pd.DataFrame()
126
+ _lock_rt = Lock()
127
+
128
+ def _getter(table, where=''):
129
+ nonlocal _lock_rt, rts
130
+ _rt = pd.read_sql(f"""select * from {LoadBalancerV3._route_schema}.{table} {where}""", db.engine)
131
+ _rt['mod_type'] = table
132
+ _lock_rt.acquire()
133
+ rts = rts.append(_rt)
134
+ _lock_rt.release()
135
+
136
+ for r in route_methods:
137
+ threads.append(Thread(target=_getter, args=(r,)))
138
+ threads[len(threads) - 1].start()
139
+ LoadBalancerV3._joiner(*threads)
140
+ LoadBalancerV3._lock_model_replace.acquire()
141
+ LoadBalancerV3.routes = rts.replace({'none': pd.np.nan})
142
+ LoadBalancerV3._lock_model_replace.release()
143
+ print('LBV3 get auto routes done')
144
+ LoadBalancerV3._lock_static_route_get.release()
145
+ else:
146
+ print('LBV3 get auto routes static lock already acquired')
147
+
148
+ def _join_lb(self):
149
+ print('join lb')
150
+ self._joiner(self._t_get_route)
151
+
152
+ @staticmethod
153
+ def async_del(lb):
154
+ print('async del')
155
+ lb._join_lb()
156
+
157
+ @staticmethod
158
+ def last_update_diff():
159
+ lst = LoadBalancerV3._last_updated
160
+ diff = (dt.datetime.now() - lst).total_seconds()
161
+ return 50000 if lst is None else (dt.datetime.now() - lst).total_seconds()
162
+
163
+ @staticmethod
164
+ def check_stale_data():
165
+ return LoadBalancerV3._max_stale_seconds < LoadBalancerV3.last_update_diff()
166
+
167
+ def init_static(self, db, route_methods=['bank_conversion',
168
+ 'iin_conversion',
169
+ 'cc_type_conversion',
170
+ 'cc_type_mcc_conversion',
171
+ 'cc_type_cc_level_conversion'],
172
+ is_rewrite=False,
173
+ **kwargs):
174
+
175
+ if LoadBalancerV3._lock_static.acquire(timeout=(0.001 if is_rewrite else 100)):
176
+
177
+ lb = LoadBalancerV3
178
+ try:
179
+ if not lb._last_updated or lb.routes is None or not len(lb.routes) or LoadBalancerV3.check_stale_data():
180
+ print('init_static')
181
+ LoadBalancerV3._last_updated = dt.datetime.now()
182
+
183
+ self._t_get_route = Thread(target=lb.get_auto_routes, args=(db, route_methods))
184
+ self._t_get_route.start()
185
+ else:
186
+ print('LBV3 cache is up to date')
187
+ except Exception as e:
188
+ print('LBV3 static init exception', str(e))
189
+
190
+ LoadBalancerV3._lock_static.release()
191
+ else:
192
+ print('LBV3 init static lock already aquired')
193
+ return LoadBalancerV3
194
+
195
+ @staticmethod
196
+ def update_static(cc_type, cc_first_6, processor, mcc, bank, level, mod_types=False):
197
+ try:
198
+ LoadBalancerV3._lock_static.acquire()
199
+ if not mod_types:
200
+ mod_types = list(LoadBalancerV3.routes.unique())
201
+ if 'iin_conversion' in mod_types:
202
+ pass
203
+ except Exception as e:
204
+ print(f'LB update static failed: {str(e)}')
205
+ LoadBalancerV3._lock_static.release()
206
+
207
+ def set_iin(self, **kwargs):
208
+
209
+ if self.iin_info is None or not len(self.iin_info):
210
+ self.iin_info = kwargs
211
+ else:
212
+ self.iin_info = {**self.iin_info, **kwargs}
213
+ if 'approved' in self.iin_info:
214
+ self.iin_info.pop('approved')
215
+ if 'declined' in self.iin_info:
216
+ self.iin_info.pop('declined')
217
+ if 'conversion_rate' in self.iin_info:
218
+ self.iin_info.pop('conversion_rate')
219
+ if 'level' not in self.iin_info:
220
+ self.iin_info['level'] = None
221
+
222
+ def next_gateway(self, crm_id, date, step, click_id='', processor=False, cc_type=None, cc_first_6=False, recurse=0,
223
+ decs=False, ignore_user_exclusions=None,
224
+ proc_excl=[], is_tds=None, **kwargs):
225
+ # opt vals 1 = random gateway constrained only by cap, 2 = optimised gateway constrained only by cap, 3 = Hybrid approach not ignoring settings, 4 = over cap (over-rides to that if needed)
226
+
227
+ if ignore_user_exclusions is None:
228
+ ignore_user_exclusions = self._opt_val < 2
229
+ if cc_first_6:
230
+ self.set_bin_info(cc_first_6)
231
+ try:
232
+ decs = self.exclude_list(crm_id, step, click_id, 'a.') if not decs else decs
233
+ except Exception as e:
234
+ return str(e)
235
+ self._joiner(self._t_get_bin)
236
+
237
+ try:
238
+ qry = self.gty_qry(crm_id, date, step, processor, cc_type, decs, proc_excl=proc_excl, is_tds=is_tds, **kwargs)
239
+ res = pd.read_sql(qry, self.engine)
240
+ cc_type = cc_type.lower()
241
+ if 'master' in cc_type:
242
+ cc_type = 'master'
243
+ if self._opt_val > 1 and self._opt_val < 4 and self.routes is not None:
244
+
245
+ self._lock_model_replace.acquire()
246
+ mod = self.routes if self.is_iin_data else self.routes.loc[
247
+ self.routes.mod_type.isin(['cc_type_conversion', 'cc_type_mcc_conversion'])]
248
+ self._lock_model_replace.release()
249
+ mod = mod.loc[(mod.approved + mod.declined >= self._min_sample_count)
250
+ & (mod.conversion_rate != 1) # take out dummy gateways
251
+ & ((mod.cc_first_6 == cc_first_6) | (mod.cc_first_6.isna()))
252
+ & (((mod.cc_type == cc_type) | mod.cc_type.isna()) if 'cc_type' in mod.columns else (
253
+ True))
254
+ & (((mod.cc_level == str(self.iin_info['level'])) | (
255
+ mod.cc_level.isna())) if self.is_iin_data and 'level' in self.iin_info else (True))
256
+ & (((mod.bank == str(self.iin_info['bank'])) | (
257
+ mod.bank.isna())) if self.is_iin_data and 'bank' in self.iin_info else (True))
258
+ ]
259
+
260
+ df_opt = mod.copy().sort_values('conversion_rate', ascending=False).reset_index(drop=True)
261
+ df_opt['r_rank'] = df_opt.index + 1
262
+
263
+ # Optimization Filters
264
+
265
+ res = res.merge(df_opt.loc[df_opt.mod_type.isin(
266
+ ['cc_type_mcc_conversion', 'bank_conversion', 'iin_conversion'])],
267
+ on=['processor', 'mcc'],
268
+ how='left').append(res.merge(
269
+ df_opt.loc[df_opt.mod_type.isin(['cc_type_cc_level_conversion', 'cc_type_conversion'])].drop('mcc',
270
+ axis=1),
271
+ on=['processor'],
272
+ how='left')).drop_duplicates()
273
+ # r_rank is Highest to lowest in terms of strength same as priority
274
+ res.mod_type = res.mod_type.fillna('undefined').replace({'nan': 'undefined', '': 'undefined'})
275
+ res.conversion_rate = res.conversion_rate.fillna(0)
276
+ else:
277
+ res['conversion_rate'] = 0
278
+ res = res.sort_values(**self.sort_map[self._opt_val]).drop_duplicates('gateway_id', keep='first')
279
+ res['cc_type'] = cc_type
280
+ res['cc_first_6'] = cc_first_6
281
+ self.set_iin(cc_first_6=cc_first_6, cc_type=cc_type)
282
+ except Exception as e:
283
+ print('LBV3 error', str(e))
284
+ raise e
285
+
286
+ if res is None or not len(res):
287
+ if not decs:
288
+ if not recurse and is_tds is not None:
289
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
290
+ is_tds=not is_tds)
291
+ elif recurse == 1:
292
+ self.init_date(date, crm_id)
293
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
294
+ is_tds=is_tds)
295
+ elif recurse == 2 and is_tds is not None:
296
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
297
+ is_tds=not is_tds)
298
+ return 'out of processing'
299
+ else:
300
+ return 'declined due to too many attempts'
301
+
302
+ r = res.loc[res.fill_pct < 1]
303
+ if 'conversion_rate' not in res:
304
+ res['conversion_rate'] = 0
305
+ # HARD CAP
306
+ if not len(r):
307
+
308
+ res = res.sort_values(['dly_initial_cap', 'conversion_rate'], ascending=[True, False]).sort_values(
309
+ ['fill_pct'])
310
+
311
+ def _get_aft_sc():
312
+ nonlocal res
313
+ if not len(res):
314
+ return 'out of processing'
315
+ r2 = res.to_dict(orient='records')[0]
316
+ if r2['initial_count_mtd'] >= r2['monthly_initial_cap']:
317
+ self.alert_cb('hard_cap_alert', crm_id=crm_id, gateway_id=r2['gateway_id'])
318
+ self.disable(crm_id=crm_id, gateway_id=r2['gateway_id'])
319
+ res = res.loc[res.gateway_id != r2['gateway_id']]
320
+ return _get_aft_sc()
321
+ self.set_iin(**r2)
322
+ r2['is_tds'] = is_tds
323
+ return r2
324
+
325
+ # SOFT CAP
326
+ if ~res.soft_cap_alerted.any():
327
+ cnt = self.engine.execute(
328
+ f"""select count(*) from {self.schema}.{self.table}
329
+ where date = '{date}'::date and crm_id = '{crm_id}'
330
+ and router_id = '{step if step == 1 else 2}'
331
+ and enabled and enable_initials and fill_pct<1
332
+ """).scalar()
333
+ if cnt == 0 or cnt is None:
334
+ self.alert_cb('soft_cap_alert', crm_id=crm_id)
335
+ self.set_soft_cap_alerted(crm_id)
336
+ return _get_aft_sc()
337
+ r = r.to_dict(orient='records')[0]
338
+ if cc_type:
339
+ r['cc_type'] = cc_type
340
+ if cc_first_6:
341
+ r['cc_first_6'] = cc_first_6
342
+
343
+ self.set_iin(**r)
344
+ r['is_tds'] = is_tds
345
+ return r
346
+
347
+ def update_models(self, approved, test=0, **kwargs):
348
+
349
+ if self.routes is None:
350
+ return
351
+
352
+ if not int(test):
353
+ self._lock_model_replace.acquire()
354
+ r = self.routes.mod_type.unique()
355
+ self._lock_model_replace.release()
356
+ for k in r:
357
+ _in = deepcopy(self.iin_info)
358
+ self.iin_info.pop('response_code',None)
359
+ getattr(self._model_class, k)(self.db_p).increment_conversion(approved, list(self.routes.columns),
360
+ **{**_in, **kwargs})
361
+
362
+ def add_test_result(self, crm_id, order_id, approved, optimised, test=0, **kwargs):
363
+ try:
364
+ self._model_class.optimised_orders(self.db_p).upsert(
365
+ pd.DataFrame([{**self.iin_info, **{'crm_id': crm_id, 'order_id': order_id, 'is_optimised': optimised,
366
+ 'is_test_cc': int(test), 'approved': int(approved), 'version':self.version}}]))
367
+ except Exception as e:
368
+ # raise e
369
+ print('LB ADD TEST RESULT ERROR', str(e))
370
+
371
+ def increment_conversion(self, date, gateway_id, crm_id, approved, order_id, **kwargs):
372
+ self.add_test_result(crm_id, order_id, approved, self._opt_val, **kwargs)
373
+ try:
374
+ self.update_models(approved, **kwargs)
375
+ except Exception as e:
376
+ pass
377
+
378
+ return self._increment_conversion(date, gateway_id, crm_id, approved, recurs_attempt=0, **kwargs)
@@ -0,0 +1,151 @@
1
+ import random
2
+ from models.db import Db, pd
3
+ from models.bro_clicks.initial_routes import ForeignInitialRoutes
4
+ from models.bro_clicks.load_balancer.v3 import LoadBalancerV3
5
+
6
+
7
+ class LoadBalancerV4(LoadBalancerV3):
8
+ def __init__(self, *args, rewrite_route='foreign_initial_route', **kw):
9
+ LoadBalancerV3.__init__(self, *args, rewrite_route=rewrite_route, **kw)
10
+ self._model_class = ForeignInitialRoutes
11
+ self._iin_schema = 'foreign_bins'
12
+ self.version = 4
13
+
14
+ def next_gateway(self, crm_id, date, step, click_id='', processor=False, cc_type=None, cc_first_6=False, recurse=0,
15
+ decs=False, ignore_user_exclusions=None,
16
+ proc_excl=[], is_tds=None, is_prepaid=True, **kwargs):
17
+ # opt vals 1 = random gateway constrained only by cap, 2 = optimised gateway constrained only by cap, 3 = Hybrid approach not ignoring settings, 4 = over cap (over-rides to that if needed)
18
+ if is_prepaid is None:
19
+ raise TypeError('is_prepaid value must be pass as a boolean got NoneType')
20
+
21
+ if is_prepaid:
22
+ pp_campaign_class = 'prepaid'
23
+ else:
24
+ pp_campaign_class = 'post_paid'
25
+ if ignore_user_exclusions is None:
26
+ ignore_user_exclusions = self._opt_val < 2
27
+ if cc_first_6:
28
+ self.set_bin_info(cc_first_6)
29
+ try:
30
+ decs = self.exclude_list(crm_id, step, click_id, 'a.') if not decs else decs
31
+ except Exception as e:
32
+ return str(e)
33
+ self._joiner(self._t_get_bin)
34
+
35
+ try:
36
+ qry = self.gty_qry(crm_id, date, step, processor, cc_type, decs, proc_excl=proc_excl, is_tds=is_tds,
37
+ is_prepaid=is_prepaid, **kwargs)
38
+ res = pd.read_sql(qry, self.engine)
39
+ cc_type = cc_type.lower()
40
+ if 'master' in cc_type:
41
+ cc_type = 'master'
42
+ if self._opt_val > 1 and self._opt_val < 4 and self.routes is not None:
43
+
44
+ self._lock_model_replace.acquire()
45
+ mod = self.routes if self.is_iin_data else self.routes.loc[
46
+ self.routes.mod_type.isin(['cc_type_conversion', 'cc_type_mcc_conversion'])]
47
+ self._lock_model_replace.release()
48
+ mod = mod.loc[(mod.approved + mod.declined >= self._min_sample_count)
49
+ # & (mod.conversion_rate != 1) # take out dummy gateways
50
+ & ((mod.cc_first_6 == cc_first_6) | (mod.cc_first_6.isna()))
51
+ & (mod.campaign_class == pp_campaign_class)
52
+ & (((mod.cc_type == cc_type) | mod.cc_type.isna()) if 'cc_type' in mod.columns else (
53
+ True))
54
+ & (((mod.cc_level == str(self.iin_info['level'])) | (
55
+ mod.cc_level.isna())) if self.is_iin_data and 'level' in self.iin_info else (True))
56
+ & (((mod.bank == str(self.iin_info['bank'])) | (
57
+ mod.bank.isna())) if self.is_iin_data and 'bank' in self.iin_info else (True))
58
+ ]
59
+ if len(mod):
60
+ df_opt = mod.copy().sort_values('conversion_rate', ascending=False).reset_index(drop=True)
61
+ df_opt['r_rank'] = df_opt.index + 1
62
+
63
+ # Optimization Filters
64
+
65
+ res = res.merge(df_opt.loc[df_opt.mod_type.isin(
66
+ ['cc_type_mcc_conversion', 'bank_conversion'])],
67
+ on=['processor', 'mcc'],
68
+ how='left').append(res.merge(
69
+ df_opt.loc[df_opt.mod_type.isin(['cc_type_cc_level_conversion', 'cc_type_conversion', 'iin_conversion'])].drop('mcc',
70
+ axis=1),
71
+ on=['processor'],
72
+ how='left')).sort_values('r_rank')
73
+ else:
74
+ res['mod_type'] = 'undefined'
75
+ res['conversion_rate'] = 0
76
+ # r_rank is Highest to lowest in terms of strength same as priority
77
+ res.mod_type = res.mod_type.fillna('undefined').replace({'nan': 'undefined', '': 'undefined'})
78
+ res.conversion_rate = res.conversion_rate.fillna(0)
79
+ else:
80
+ res['conversion_rate'] = 0
81
+ res['mod_type'] = 'undefined'
82
+ res = res.sort_values(**self.sort_map[self._opt_val]).drop_duplicates('gateway_id', keep='first')
83
+ res['cc_type'] = cc_type
84
+ res['cc_first_6'] = cc_first_6
85
+ self.set_iin(cc_first_6=cc_first_6, cc_type=cc_type)
86
+ except Exception as e:
87
+ print('LBV4 error', str(e))
88
+ raise e
89
+
90
+ if res is None or not len(res):
91
+ if not decs:
92
+ if not recurse and is_tds is not None:
93
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
94
+ is_tds=not is_tds, is_prepaid=is_prepaid)
95
+ elif recurse == 1:
96
+ self.init_date(date, crm_id)
97
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
98
+ is_tds=is_tds, is_prepaid=is_prepaid)
99
+ elif recurse == 2 and is_tds is not None:
100
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
101
+ is_tds=not is_tds, is_prepaid=is_prepaid)
102
+ return 'out of processing'
103
+ else:
104
+ # if len(decs) < 4:
105
+ # return 'out of processing'
106
+ return 'declined due to too many attempts'
107
+ r = res.loc[res.fill_pct < 1]
108
+ if 'conversion_rate' not in res:
109
+ res['conversion_rate'] = 0
110
+ # HARD CAP
111
+ if not len(r):
112
+
113
+ res = res.sort_values(['dly_initial_cap', 'conversion_rate'], ascending=[True, False]).sort_values(
114
+ ['fill_pct'])
115
+
116
+ def _get_aft_sc():
117
+ nonlocal res
118
+ if not len(res):
119
+ return 'out of processing'
120
+ r2 = res.to_dict(orient='records')[0]
121
+ if r2['initial_count_mtd'] >= r2['monthly_initial_cap']:
122
+ self.alert_cb('hard_cap_alert', crm_id=crm_id, gateway_id=r2['gateway_id'])
123
+ self.disable(crm_id=crm_id, gateway_id=r2['gateway_id'])
124
+ res = res.loc[res.gateway_id != r2['gateway_id']]
125
+ return _get_aft_sc()
126
+ self.set_iin(**r2)
127
+ r2['is_tds'] = is_tds
128
+ return r2
129
+
130
+ # SOFT CAP
131
+ if ~res.soft_cap_alerted.any():
132
+ cnt = self.engine.execute(
133
+ f"""select count(*) from {self.schema}.{self.table}
134
+ where date = '{date}'::date and crm_id = '{crm_id}'
135
+ and router_id = '{step if step == 1 else 2}'
136
+ and enabled and enable_initials and fill_pct<1
137
+ """).scalar()
138
+ if cnt == 0 or cnt is None:
139
+ self.alert_cb('soft_cap_alert', crm_id=crm_id)
140
+ self.set_soft_cap_alerted(crm_id)
141
+ return _get_aft_sc()
142
+ r = r.to_dict(orient='records')[0]
143
+ if cc_type:
144
+ r['cc_type'] = cc_type
145
+ if cc_first_6:
146
+ r['cc_first_6'] = cc_first_6
147
+
148
+ self.set_iin(**r)
149
+ r['is_tds'] = is_tds
150
+ return r
151
+
@@ -0,0 +1,123 @@
1
+ import pandas as pd
2
+ from models.bro_clicks.initial_routes import InitialRoutes, ForeignInitialRoutes
3
+ from models.bro_clicks.load_balancer.v4 import LoadBalancerV4
4
+
5
+
6
+ class LoadBalancerV5(LoadBalancerV4):
7
+ def __init__(self, *args, **kw):
8
+ LoadBalancerV4.__init__(self, *args, **kw)
9
+ self._model_class = ForeignInitialRoutes
10
+ self._iin_schema = 'foreign_bins'
11
+ self.version = 5
12
+
13
+ def init_date(self, date, crm_id, reset_cap_count=True):
14
+ sk = f'ui_{self._account_id}_clients'
15
+ real_cap_space = self.get_processing_for_month(crm_id) if reset_cap_count else None
16
+
17
+ qry = f"""
18
+ SELECT '{date}'::date as date, b.crm_id, a.mid_id, b.gateway_id, b.step, a.processor,
19
+ coalesce(e.approved, 0) approved, coalesce(e.approved,0) initial_count, c.dly_initial_cap, b.minimum_price, coalesce(e.declined, 0) declined,
20
+ d.approval_rate, c.dly_min_approval_rate, array_to_string(c.pr_exclude_cc_types, ',') exclude_cc_types ,
21
+ c.date_added, c.enable_tds, array_to_string(c.tds_exclude_cc_types, ',') tds_exclude_cc_types, c.enabled, c.enable_initials, c.monthly_initial_cap, c.priority, c.router_id, d.router_id as cur_router_id,
22
+ c.allow_prepaid, c.allow_non_prepaid,
23
+ d.soft_cap_alerted, d.initial_count_mtd as prev_mtd
24
+
25
+ FROM {sk}.mids a
26
+ LEFT JOIN {sk}.steps b on b.mid_id = a.mid_id
27
+ LEFT JOIN {sk}.gateway_settings c on c.gateway_id = b.gateway_id and c.crm_id = b.crm_id
28
+ LEFT JOIN {self.schema}.{self.table} d on c.gateway_id =d.gateway_id and c.crm_id = d.crm_id and b.step = d.step and '{date}'::date =d.date
29
+ LEFT JOIN (select crm_id, gateway_id, coalesce(sum(declined), 0) declined, coalesce(sum(approved), 0) approved
30
+ from {self.schema}.conversions where coalesce(test, 0) <> 1 and time_stamp::date = '{date}'::date group by crm_id, gateway_id
31
+ ) e on e.gateway_id =c.gateway_id and e.crm_id=c.crm_id
32
+ where (b.close_date is null or b.close_date >'{self.today()}')
33
+ and b.crm_id = '{crm_id}'
34
+ and b.gateway_id is not null
35
+ and a.processor not ilike '%%virtual%%'
36
+ and b.gateway_id::int <> 1
37
+ and a.processor != 'FlexCharge'
38
+
39
+ """
40
+
41
+ try:
42
+
43
+ # if crm_id != 'crm_ll_2':
44
+ #
45
+
46
+ # print(qry)
47
+ # print('break')
48
+ up = pd.read_sql(qry, self.engine)
49
+
50
+ up = up.sort_values('step').drop_duplicates(['gateway_id', 'cur_router_id'], keep='first')
51
+ up = up.loc[~up.router_id.isna()]
52
+ up = up.explode('router_id')
53
+
54
+ # delete changes to routers
55
+ del_gt_msk = (up.router_id != up.cur_router_id) & (
56
+ up.gateway_id.isin(up.loc[~up.cur_router_id.isna()].gateway_id.unique()))
57
+ del_gtys = up.loc[del_gt_msk].gateway_id.tolist()
58
+
59
+ up = up.loc[(~up.gateway_id.isin(del_gtys)) | (~up.cur_router_id.isna())]
60
+
61
+ # delete changes to routers
62
+ del_gt_msk = (up.router_id != up.cur_router_id)
63
+ del_gtys = up.loc[del_gt_msk].gateway_id.tolist()
64
+ self.engine.execute(
65
+ f"delete from {self.schema}.{self.table} where gateway_id::int = ANY(ARRAY{del_gtys}::int[]) and crm_id='{crm_id}'")
66
+ up = up.drop(columns='cur_router_id')
67
+ except Exception as e:
68
+ raise e
69
+ if reset_cap_count:
70
+ try:
71
+ up = up.merge(real_cap_space, on=['gateway_id'], how='left')
72
+ up.initial_count_mtd = up.initial_count_mtd.fillna(0)
73
+ up.initial_count_mtd += up.initial_count
74
+
75
+ except:
76
+ up['initial_count_mtd'] = up.prev_mtd.fillna(0)
77
+ up.initial_count_mtd = up.initial_count_mtd.fillna(0)
78
+
79
+ drim = float(self.get_drim())
80
+ up.dly_initial_cap = pd.np.floor((up.monthly_initial_cap - up.initial_count_mtd) / drim)
81
+ up.loc[up.dly_initial_cap < 0, 'dly_initial_cap'] = 0
82
+
83
+ up.dly_initial_cap = up.dly_initial_cap.fillna(11)
84
+ up.dly_min_approval_rate = up.dly_min_approval_rate.fillna(30)
85
+ up.declined = up.declined.fillna(0)
86
+ up.approval_rate = up.approval_rate.fillna(0)
87
+ up.soft_cap_alerted = up.soft_cap_alerted.fillna(False)
88
+ up.drop('prev_mtd', axis=1, errors='ignore', inplace=True)
89
+ up = up.drop_duplicates(['gateway_id', 'router_id'])
90
+ # self.engine.execute(f'truncate {self.schema}.{self.table}')
91
+ self.upsert(up.dropna())
92
+
93
+ def gty_qry(self, crm_id, date, step, processor, cc_type=False, decs='', proc_excl=[], is_tds=None,
94
+ is_prepaid=False, is_decline_salvage=False, **kw):
95
+ p_ex = ''
96
+ if proc_excl and len(proc_excl) and not processor:
97
+ p_ex = f"and a.processor not ilike all(ARRAY{[f'%%{p}%%' for p in proc_excl]}::text[])"
98
+
99
+ return f""" --LEFT HERE NEED TO GET MCC!
100
+ select a.gateway_id::int, a.fill_pct,a.dly_initial_cap,priority,initial_count, a.approval_rate, a.date_added, a.processor, a.mid_id, a.monthly_initial_cap, a.soft_cap_alerted, initial_count_mtd,b.mcc from {self.schema}.{self.table} a
101
+ inner join (select crm_id, gateway_id, mcc from ui_54407332_clients.steps ) b on b.crm_id = a.crm_id and b.gateway_id=a.gateway_id
102
+ {"inner join (select crm_id, gateway_id where enable_decline_salvage) ds on ds.crm_id = a.crm_id and ds.gateway_id::int = a.gateway_id::int" if is_decline_salvage else "" }
103
+ inner join processing.cap c on a.mid_id = c.mid_id and a.step=c.step and a.processor=c.processor and c.monthly_available > 200
104
+ {f"left join processing.cap_cc_type d on a.mid_id = d.mid_id and a.step= d.step and a.processor = d.processor and d.cc_type = '{cc_type}' " if cc_type else ''}
105
+
106
+ where date = '{date}'::date and a.crm_id = '{crm_id}' and router_id = '{step if step in [1, 11] else 2}' and enabled and enable_initials
107
+ {f"and a.processor = '{processor}'" if processor else ""}
108
+ {f"and (exclude_cc_types is null or exclude_cc_types::text not ilike '%%{cc_type.lower()}%%')" if cc_type else ''}
109
+ and (approval_rate > dly_min_approval_rate or(declined+initial_count<110))
110
+ {'and (d.available_tc is null or d.available_tc >50)' if cc_type else ''}
111
+ {decs}
112
+ {p_ex}
113
+ {f"and enable_tds = {bool(is_tds)}" if is_tds else ""}
114
+ {f"and (tds_exclude_cc_types is null or tds_exclude_cc_types not ilike '%%{cc_type}%%')" if cc_type and is_tds else ""}
115
+ {f"and allow_prepaid" if is_prepaid else ""}
116
+ {f"and allow_non_prepaid" if not is_prepaid else ""}
117
+ --and fill_pct < 1
118
+
119
+ --order by date_added desc, approval_rate desc, fill_pct asc limit 1
120
+ order by priority desc, date_added desc, fill_pct, approval_rate desc, initial_count
121
+
122
+ """
123
+
@@ -0,0 +1,192 @@
1
+ from models.bro_clicks.load_balancer import LoadBalancerV5, LoadBalancerV3
2
+ from models.bro_clicks.initial_routes import ForeignInitialRoutesV2
3
+
4
+ from threading import Thread
5
+ import pandas as pd
6
+ from concurrent.futures import ThreadPoolExecutor, TimeoutError
7
+ import time
8
+
9
+ class LoadBalancerV6(LoadBalancerV5):
10
+ #LoadBalancerV3._route_schema = 'foreign_initial_route_v2'
11
+ def __init__(self, *args, **kw):
12
+ LoadBalancerV5.__init__(self, *args, rewrite_route='foreign_initial_route_v2', **kw)
13
+ self._model_class = ForeignInitialRoutesV2
14
+ self._iin_schema = 'foreign_bins'
15
+ self.version=6
16
+
17
+
18
+ def set_bin_info(self, cc_first_8):
19
+ def _exec():
20
+ nonlocal cc_first_8
21
+ self.iin_info = {'cc_first_8': cc_first_8}
22
+ try:
23
+ iini = pd.read_sql(f"select * from {self._iin_schema}.iin8_data where iin='{cc_first_8}'",
24
+ self.db_p.engine).astype(str).applymap(str.lower).replace({'none': None})
25
+ if len(iini):
26
+ self.iin_info = iini
27
+ self.is_iin_data = True
28
+ self.iin_info = self.iin_info.to_dict(orient='records')[0]
29
+ if self.iin_info['bank_map'] is not None:
30
+ self.iin_info['bank'] = self.iin_info['bank_map']
31
+ if 'level' not in self.iin_info:
32
+ self.iin_info['level'] = None
33
+ else:
34
+ self.iin_info = {}
35
+ except Exception as e:
36
+ print(f'SET BIN INFO ERROR: str(e)')
37
+
38
+ self.iin_info['cc_first_8'] = cc_first_8
39
+
40
+ with ThreadPoolExecutor(max_workers=1) as executor:
41
+ self._t_get_bin = executor.submit(_exec) # Task that takes 5 seconds
42
+
43
+
44
+
45
+
46
+ def next_gateway(self, crm_id, date, step, click_id='', processor=False, cc_type=None, cc_first_8=False, recurse=0,
47
+ decs=False, ignore_user_exclusions=None,
48
+ proc_excl=[], is_tds=None, is_prepaid=True, **kwargs):
49
+ # opt vals 1 = random gateway constrained only by cap, 2 = optimised gateway constrained only by cap, 3 = Hybrid approach not ignoring settings, 4 = over cap (over-rides to that if needed)
50
+ if is_prepaid is None:
51
+ raise TypeError('is_prepaid value must be pass as a boolean got NoneType')
52
+
53
+ if is_prepaid:
54
+ pp_campaign_class = 'prepaid'
55
+ else:
56
+ pp_campaign_class = 'post_paid'
57
+ if ignore_user_exclusions is None:
58
+ ignore_user_exclusions = self._opt_val < 2
59
+ if cc_first_8:
60
+ self.set_bin_info(cc_first_8)
61
+ try:
62
+ decs = self.exclude_list(crm_id, step, click_id, 'a.') if not decs else decs
63
+ except Exception as e:
64
+ return str(e)
65
+
66
+
67
+ try:
68
+ qry = self.gty_qry(crm_id, date, step, processor, cc_type, decs, proc_excl=proc_excl, is_tds=is_tds,
69
+ is_prepaid=is_prepaid, **kwargs)
70
+ res = pd.read_sql(qry, self.engine)
71
+ cc_type = cc_type.lower()
72
+ if 'master' in cc_type:
73
+ cc_type = 'master'
74
+ try:
75
+ result = self._t_get_bin.result(timeout=4) # Wait for 2 seconds
76
+ except TimeoutError:
77
+ print("IIN INFO timed out!")
78
+ except Exception as e:
79
+ print(f"An error occurred: {e}")
80
+ if self._opt_val > 1 and self._opt_val < 4 and self.routes is not None:
81
+
82
+ self._lock_model_replace.acquire()
83
+ mod = self.routes if self.is_iin_data else self.routes.loc[
84
+ self.routes.mod_type.isin(['cc_type_conversion', 'cc_type_mcc_conversion'])]
85
+ self._lock_model_replace.release()
86
+ mod = mod.loc[(mod.approved + mod.declined >= self._min_sample_count)
87
+ # & (mod.conversion_rate != 1) # take out dummy gateways
88
+ & ((mod.cc_first_8 == cc_first_8) | (mod.cc_first_8.isna()))
89
+ & (mod.campaign_class == pp_campaign_class)
90
+ & (((mod.cc_type == cc_type) | mod.cc_type.isna()) if 'cc_type' in mod.columns else (
91
+ True))
92
+ & (((mod.cc_level == str(self.iin_info['level'])) | (
93
+ mod.cc_level.isna())) if self.is_iin_data and 'level' in self.iin_info else (True))
94
+ & (((mod.bank == str(self.iin_info['bank'])) | (
95
+ mod.bank.isna())) if self.is_iin_data and 'bank' in self.iin_info else (True))
96
+ ]
97
+ if len(mod): #and 'conversion_rate' in mod.columns:
98
+ df_opt = mod.copy().sort_values('conversion_rate', ascending=False).reset_index(drop=True)
99
+ df_opt['r_rank'] = df_opt.index + 1
100
+
101
+ # Optimization Filters
102
+
103
+ res = res.merge(df_opt.loc[df_opt.mod_type.isin(
104
+ ['cc_type_mcc_conversion', 'bank_conversion'])],
105
+ on=['processor', 'mcc'],
106
+ how='left').append(res.merge(
107
+ df_opt.loc[df_opt.mod_type.isin(['cc_type_cc_level_conversion', 'cc_type_conversion', 'iin_conversion'])].drop('mcc',
108
+ axis=1),
109
+ on=['processor'],
110
+ how='left')).sort_values('r_rank')
111
+ else:
112
+ res['mod_type'] = 'undefined'
113
+ res['conversion_rate'] = 0
114
+ # r_rank is Highest to lowest in terms of strength same as priority
115
+ res.mod_type = res.mod_type.fillna('undefined').replace({'nan': 'undefined', '': 'undefined'})
116
+ res.conversion_rate = res.conversion_rate.fillna(0)
117
+ else:
118
+ res['conversion_rate'] = 0
119
+ res['mod_type'] = 'undefined'
120
+ res = res.sort_values(**self.sort_map[self._opt_val]).drop_duplicates('gateway_id', keep='first')
121
+ res['cc_type'] = cc_type
122
+ res['cc_first_8'] = cc_first_8
123
+ self.set_iin(cc_first_8=cc_first_8, cc_type=cc_type)
124
+ except Exception as e:
125
+ print('LBV4 error', str(e))
126
+ raise e
127
+
128
+ if res is None or not len(res):
129
+ if not decs:
130
+ if not recurse and is_tds is not None:
131
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
132
+ is_tds=not is_tds, is_prepaid=is_prepaid)
133
+ elif recurse == 1:
134
+ self.init_date(date, crm_id)
135
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
136
+ is_tds=is_tds, is_prepaid=is_prepaid)
137
+ elif recurse == 2 and is_tds is not None:
138
+ return self.next_gateway(crm_id, date, step, click_id, processor, cc_type, recurse=recurse + 1,
139
+ is_tds=not is_tds, is_prepaid=is_prepaid)
140
+ return 'out of processing'
141
+ else:
142
+ # if len(decs) < 4:
143
+ # return 'out of processing'
144
+ return 'declined due to too many attempts'
145
+ r = res.loc[res.fill_pct < 1]
146
+ if 'conversion_rate' not in res:
147
+ res['conversion_rate'] = 0
148
+ # HARD CAP
149
+ if not len(r):
150
+
151
+ res = res.sort_values(['dly_initial_cap', 'conversion_rate'], ascending=[True, False]).sort_values(
152
+ ['fill_pct'])
153
+
154
+ def _get_aft_sc():
155
+ nonlocal res
156
+ if not len(res):
157
+ return 'out of processing'
158
+ r2 = res.to_dict(orient='records')[0]
159
+ if r2['initial_count_mtd'] >= r2['monthly_initial_cap']:
160
+ self.alert_cb('hard_cap_alert', crm_id=crm_id, gateway_id=r2['gateway_id'])
161
+ self.disable(crm_id=crm_id, gateway_id=r2['gateway_id'])
162
+ res = res.loc[res.gateway_id != r2['gateway_id']]
163
+ return _get_aft_sc()
164
+ self.set_iin(**r2)
165
+ r2['is_tds'] = is_tds
166
+ return r2
167
+
168
+ # SOFT CAP
169
+ if ~res.soft_cap_alerted.any():
170
+ cnt = self.engine.execute(
171
+ f"""select count(*) from {self.schema}.{self.table}
172
+ where date = '{date}'::date and crm_id = '{crm_id}'
173
+ and router_id = '{step if step == 1 else 2}'
174
+ and enabled and enable_initials and fill_pct<1
175
+ """).scalar()
176
+ if cnt == 0 or cnt is None:
177
+ self.alert_cb('soft_cap_alert', crm_id=crm_id)
178
+ self.set_soft_cap_alerted(crm_id)
179
+ return _get_aft_sc()
180
+ r = r.to_dict(orient='records')[0]
181
+ if cc_type:
182
+ r['cc_type'] = cc_type
183
+ if cc_first_8:
184
+ r['cc_first_8'] = cc_first_8
185
+
186
+ self.set_iin(**r)
187
+ r['is_tds'] = is_tds
188
+ return r
189
+
190
+
191
+
192
+
@@ -0,0 +1,8 @@
1
+ import random
2
+ from models.db import Db, pd
3
+ from models.bro_clicks.initial_routes import InitialRoutes, ForeignInitialRoutes
4
+ from calendar import monthrange
5
+ import datetime as dt
6
+ from models import config
7
+ from threading import Thread, Lock
8
+ from copy import deepcopy
@@ -0,0 +1,8 @@
1
+ import random
2
+ from models.db import Db, pd
3
+ from models.bro_clicks.initial_routes import InitialRoutes, ForeignInitialRoutes
4
+ from calendar import monthrange
5
+ import datetime as dt
6
+ from models import config
7
+ from threading import Thread, Lock
8
+ from copy import deepcopy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelswrkpi
3
- Version: 8.0.5
3
+ Version: 8.1.1
4
4
  Author: Kelly Kapowski
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: psycopg2
@@ -62,10 +62,25 @@ models/bro_clicks/binrouter.cpython-310-x86_64-linux-gnu.so,sha256=3RU-R5hUed5yW
62
62
  models/bro_clicks/clicks.cpython-310-x86_64-linux-gnu.so,sha256=aj4yUIKZqUAtHxKXn0IIl0qQDSbOYrU3HeeF8ecXf0g,107184
63
63
  models/bro_clicks/conversions.cpython-310-x86_64-linux-gnu.so,sha256=yvvBXlDVd5GQG0AuATp7S6jctO0abLxElr72OcVDaH4,98896
64
64
  models/bro_clicks/error_log.cpython-310-x86_64-linux-gnu.so,sha256=I8fwpBrbxaB_A8JfIyHo0Z4A3ha3ejon9vkutq63op0,65936
65
- models/bro_clicks/initial_routes.cpython-310-x86_64-linux-gnu.so,sha256=-HgXizWxJs-kRMBLs1jgdeDZBHCUdsbNzFaVK2RvZaY,127952
66
- models/bro_clicks/load_balancer.cpython-310-x86_64-linux-gnu.so,sha256=pITO9cKQ5KH9OGbu42fRGsjJRbO6_2aTlh2unXhXAIw,549008
65
+ models/bro_clicks/initial_routes.cpython-310-x86_64-linux-gnu.so,sha256=JEM-67AUh3VsAJ9a_rH37jH4tCqdX-E6NMYgiLo9mHg,140368
67
66
  models/bro_clicks/scrub_settings.cpython-310-x86_64-linux-gnu.so,sha256=gKo6LHc-3OCQRl1SAg9XH6XMBJujLoB5F-6sYgKjTZ0,160592
68
67
  models/bro_clicks/splitter.cpython-310-x86_64-linux-gnu.so,sha256=27kzt-JDllwAXMFq8Xe2Av0RIDpwFK4h_t6BvytGsak,57712
68
+ models/bro_clicks/load_balancer/__init__.py,sha256=h0UIw3AFm-gdyK-3xR1NgFKC95sJW1hz2Pw2dMdkA3A,370
69
+ models/bro_clicks/load_balancer/compile.py,sha256=M1o0NKWcQe6b27e96PZlNUBBe8EFP5CnI3cWDamzquk,600
70
+ models/bro_clicks/load_balancer/v1.cpython-310-x86_64-linux-gnu.so,sha256=IJa5FkmwEsQXh8ULGGjoP_hPbxkXWw1KABwmVV1YDUc,160656
71
+ models/bro_clicks/load_balancer/v1.py,sha256=Df-LMtS0l0psJCyguSHtSIt_ofLwop55ogWDq3eFrig,8518
72
+ models/bro_clicks/load_balancer/v2.cpython-310-x86_64-linux-gnu.so,sha256=VCGHr8KSipO2E0IfJ0zSpdte-xw_xsSK5vRWujtumnU,140368
73
+ models/bro_clicks/load_balancer/v2.py,sha256=WSM6lKtyLrzx1xdhL3ncuH4QH-FBnS5Gm0uDFAtNHXo,7303
74
+ models/bro_clicks/load_balancer/v3.cpython-310-x86_64-linux-gnu.so,sha256=f03MbM9KyuKpctvJ-6g6pnhMbnsasPtTkFVeQlRXUeA,256496
75
+ models/bro_clicks/load_balancer/v3.py,sha256=-seP7t7dVO-O2q4I3wqgKrgTKxGPWVROHvSzVgwd_ug,16542
76
+ models/bro_clicks/load_balancer/v4.cpython-310-x86_64-linux-gnu.so,sha256=kbQws2ObO2BxOOxjCaB7_H8IA1allHZ-zsX2kYMvrJo,136208
77
+ models/bro_clicks/load_balancer/v4.py,sha256=wGe66SzYIa4qiPnA4ZZQ5tisvXv_j7XZZCVrsCG23Eg,7732
78
+ models/bro_clicks/load_balancer/v5.cpython-310-x86_64-linux-gnu.so,sha256=VkmSqG11rgZTOv5AzhhVga52Ay6rrRKqmrrFVj8gUl0,127568
79
+ models/bro_clicks/load_balancer/v5.py,sha256=GLRcbOUAI4n2m8wv7yGNhRZoPHQ8WO_Ra4FeAr3Yxsg,7755
80
+ models/bro_clicks/load_balancer/v6.cpython-310-x86_64-linux-gnu.so,sha256=aOxX26fPb0UvTrRrilI2a35ojbbaq4BoDrfPiNLsAk4,165360
81
+ models/bro_clicks/load_balancer/v6.py,sha256=G7kA18-PA5NA2WZMIRChPkFS-JbyZcqLzgFtu4mZxEs,9334
82
+ models/bro_clicks/load_balancer/v7.py,sha256=tkk6FpLgQvKCaFf2kmKsHhW_Pj4gJq4PcSe-DKcIUC4,264
83
+ models/bro_clicks/load_balancer/v8.py,sha256=tkk6FpLgQvKCaFf2kmKsHhW_Pj4gJq4PcSe-DKcIUC4,264
69
84
  models/cb911/__init__.py,sha256=Y3tX5WQYFUswdMMUpWqa9U_fNObUAhXcVrix3eXYP4k,64
70
85
  models/cb911/alerts.cpython-310-x86_64-linux-gnu.so,sha256=LCsUfaTzdQKS6ZaKQbfLCNTZbpZqaJmEsWDTUOdHUrI,61808
71
86
  models/cb911/cb_struc.cpython-310-x86_64-linux-gnu.so,sha256=X6UcgfU0Q7FPiiG-zzJcoefGj91RVRA1kR6ZMHu_1Gc,57744
@@ -112,7 +127,7 @@ models/reports/traffic_reporting.cpython-310-x86_64-linux-gnu.so,sha256=Fu-CrJkD
112
127
  models/reports/widgets.cpython-310-x86_64-linux-gnu.so,sha256=BglXB0vfh8GeQqZPrv4BsnV90YKJI9YdqOFbJv-RgwM,74160
113
128
  models/reports/table_reports/__init__.py,sha256=prIxlfuVxK30sTDk7VwF7h9E4rVOCq7yDwYr3f0IXWA,522
114
129
  models/reports/table_reports/affid_report.cpython-310-x86_64-linux-gnu.so,sha256=UpVBJQZHszViYsFNqMNUDN9Aeux-2GTRcgKB2L9IGP8,74032
115
- models/reports/table_reports/approval_report.cpython-310-x86_64-linux-gnu.so,sha256=UVHzR-MaQjjVc0dMQHqmwJoy_hQa715h6sOVed6fXCY,69968
130
+ models/reports/table_reports/approval_report.cpython-310-x86_64-linux-gnu.so,sha256=8jefe_dfX0aq9gRgD4DZD6Zk-6teuj-iMoUICVCSHm0,69968
116
131
  models/reports/table_reports/bin_report.cpython-310-x86_64-linux-gnu.so,sha256=tZcFVieD_UbivTqm_t5XPZvbVi_lPrMnmODsP9woptQ,65840
117
132
  models/reports/table_reports/continuity_report.cpython-310-x86_64-linux-gnu.so,sha256=KU85x0U6Ud7coh3yQ50yEYiTiE-zdkG7tVoh-QuTUTg,61744
118
133
  models/reports/table_reports/impact_reports.cpython-310-x86_64-linux-gnu.so,sha256=NKozkFYR2vn5yE7uURfOY9b312BM-soBe2i83HbqSNM,82320
@@ -120,7 +135,7 @@ models/reports/table_reports/inactive_report.cpython-310-x86_64-linux-gnu.so,sha
120
135
  models/reports/table_reports/mtd_report.cpython-310-x86_64-linux-gnu.so,sha256=dHko7wWia9y9d-gIPC8uxycUnobII6hQgairSPRU168,74096
121
136
  models/vaultx/__init__.py,sha256=PwYMmWXQMojeyFnQuQW3wNlv9dLUmX_uiDfCOHJN7XI,46
122
137
  models/vaultx/ssc_cont.cpython-310-x86_64-linux-gnu.so,sha256=69kGuEko6oJEqxopS_4BxoX5S-Twq94eFwYJFEu_4B8,94992
123
- modelswrkpi-8.0.5.dist-info/METADATA,sha256=LAhdCYNny8H9bwFleC7Jg9vQCelcv8JryBVYiO2DAaw,346
124
- modelswrkpi-8.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
125
- modelswrkpi-8.0.5.dist-info/top_level.txt,sha256=fOgLNy4xnMfp0ihe_kFhY_yH71kNkdEQG68z8FoLR4g,19
126
- modelswrkpi-8.0.5.dist-info/RECORD,,
138
+ modelswrkpi-8.1.1.dist-info/METADATA,sha256=Ta6Ilu1KFfT8Rm6PoabwUPIGx40_VIkc3AQRZ3qWw0o,346
139
+ modelswrkpi-8.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
140
+ modelswrkpi-8.1.1.dist-info/top_level.txt,sha256=fOgLNy4xnMfp0ihe_kFhY_yH71kNkdEQG68z8FoLR4g,19
141
+ modelswrkpi-8.1.1.dist-info/RECORD,,