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.
- models/bro_clicks/initial_routes.cpython-310-x86_64-linux-gnu.so +0 -0
- models/bro_clicks/load_balancer/__init__.py +6 -0
- models/bro_clicks/load_balancer/compile.py +16 -0
- models/bro_clicks/load_balancer/v1.cpython-310-x86_64-linux-gnu.so +0 -0
- models/bro_clicks/load_balancer/v1.py +176 -0
- models/bro_clicks/load_balancer/v2.cpython-310-x86_64-linux-gnu.so +0 -0
- models/bro_clicks/load_balancer/v2.py +128 -0
- models/bro_clicks/load_balancer/v3.cpython-310-x86_64-linux-gnu.so +0 -0
- models/bro_clicks/load_balancer/v3.py +378 -0
- models/bro_clicks/load_balancer/v4.cpython-310-x86_64-linux-gnu.so +0 -0
- models/bro_clicks/load_balancer/v4.py +151 -0
- models/bro_clicks/load_balancer/v5.cpython-310-x86_64-linux-gnu.so +0 -0
- models/bro_clicks/load_balancer/v5.py +123 -0
- models/bro_clicks/load_balancer/v6.cpython-310-x86_64-linux-gnu.so +0 -0
- models/bro_clicks/load_balancer/v6.py +192 -0
- models/bro_clicks/load_balancer/v7.py +8 -0
- models/bro_clicks/load_balancer/v8.py +8 -0
- models/reports/table_reports/approval_report.cpython-310-x86_64-linux-gnu.so +0 -0
- {modelswrkpi-8.0.5.dist-info → modelswrkpi-8.1.1.dist-info}/METADATA +1 -1
- {modelswrkpi-8.0.5.dist-info → modelswrkpi-8.1.1.dist-info}/RECORD +22 -7
- models/bro_clicks/load_balancer.cpython-310-x86_64-linux-gnu.so +0 -0
- {modelswrkpi-8.0.5.dist-info → modelswrkpi-8.1.1.dist-info}/WHEEL +0 -0
- {modelswrkpi-8.0.5.dist-info → modelswrkpi-8.1.1.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
@@ -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
|
+
)
|
|
Binary file
|
|
@@ -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}'")
|
|
Binary file
|
|
@@ -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
|
+
|
|
Binary file
|
|
@@ -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)
|
|
Binary file
|
|
@@ -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
|
+
|
|
Binary file
|
|
@@ -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
|
+
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
@@ -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
|
|
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=
|
|
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.
|
|
124
|
-
modelswrkpi-8.
|
|
125
|
-
modelswrkpi-8.
|
|
126
|
-
modelswrkpi-8.
|
|
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,,
|
|
Binary file
|
|
File without changes
|
|
File without changes
|