Subversion Repositories SmartDukaan

Rev

Rev 14466 | Rev 14549 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
13774 amit.gupta 1
# coding=utf-8
13569 amit.gupta 2
'''
3
Created on Jan 15, 2015
4
 
5
@author: amit
6
'''
13774 amit.gupta 7
from base64 import encode
8
from bs4 import BeautifulSoup
14162 amit.gupta 9
from datetime import datetime
13774 amit.gupta 10
from dtr.dao import Order, SubOrder
14464 amit.gupta 11
from dtr.main import getStore, Store as MStore, ParseException, getBrowserObject, \
12
    ungzipResponse
13774 amit.gupta 13
from dtr.sources.flipkart import todict
14162 amit.gupta 14
from paramiko import sftp
15
from paramiko.client import SSHClient
16
from paramiko.sftp_client import SFTPClient
14285 amit.gupta 17
import os.path
14162 amit.gupta 18
import paramiko
13774 amit.gupta 19
import re
14033 amit.gupta 20
import time
13809 amit.gupta 21
import traceback
13774 amit.gupta 22
 
23
ORDER_REDIRECT_URL = 'https://www.amazon.in/gp/css/summary/edit.html?orderID=%s'
13810 amit.gupta 24
ORDER_SUCCESS_URL = 'https://www.amazon.in/gp/buy/spc/handlers/static-submit-decoupled.html'
13823 amit.gupta 25
THANKYOU_URL = 'https://www.amazon.in/gp/buy/thankyou/handlers/display.html'
13774 amit.gupta 26
class Store(MStore):
13821 amit.gupta 27
 
14032 amit.gupta 28
    orderStatusRegexMap = { MStore.ORDER_PLACED : ['ordered from', 'not yet dispatched','dispatching now', 'preparing for dispatch'],
14064 amit.gupta 29
                            MStore.ORDER_SHIPPED : ['dispatched on','dispatched', 'on the way', 'out for delivery', 'Out for delivery'],
14032 amit.gupta 30
                            MStore.ORDER_CANCELLED : ['return complete', 'refunded', 'cancelled'],
31
                            MStore.ORDER_DELIVERED : ['delivered']
13821 amit.gupta 32
                           }
13774 amit.gupta 33
 
34
    def __init__(self,store_id):
35
        super(Store, self).__init__(store_id)
36
 
37
    def getName(self):
38
        return "flipkart"
39
 
14202 amit.gupta 40
    def parseOrderRawHtml(self, orderId, subTagId, userId, rawHtml, orderSuccessUrl, insert=False):
13796 amit.gupta 41
        resp = {}
13821 amit.gupta 42
        if ORDER_SUCCESS_URL in orderSuccessUrl or THANKYOU_URL in orderSuccessUrl:
13774 amit.gupta 43
            try:
44
                soup = BeautifulSoup(rawHtml)
45
                orderUrl = soup.find('div', {"id":"thank-you-box-wrapper"}).div.findAll('div', recursive=False)[1].a['href']
14202 amit.gupta 46
                merchantOrderId = re.findall(r'.*&oid=(.*)&?.*?', orderUrl)[0]
14211 amit.gupta 47
                order = Order(orderId, userId, subTagId, self.store_id, orderSuccessUrl, False)
48
                order.orderSuccessUrl = ORDER_REDIRECT_URL % (merchantOrderId)
49
                order.merchantOrderId = merchantOrderId
14291 amit.gupta 50
                order.requireDetail = True
14297 amit.gupta 51
                order.closed = None
14312 amit.gupta 52
                if self._saveToOrder(todict(order)):
53
                    resp['result'] = 'ORDER_CREATED'
54
                    resp["url"] = ORDER_REDIRECT_URL % (merchantOrderId)
55
                    resp["htmlRequired"] = True
56
                    resp['orderId'] = orderId
57
                else:
58
                    resp['result'] = 'ORDER_ALREADY_CREATED_IGNORED'
59
 
13796 amit.gupta 60
                return resp
13774 amit.gupta 61
            except:
14312 amit.gupta 62
                resp["result"] = 'ORDER_NOT_CREATED'
13796 amit.gupta 63
                return resp
13774 amit.gupta 64
 
65
        else:
13781 amit.gupta 66
            try:
67
                merchantOrder = Order(orderId, userId, subTagId, self.store_id, orderSuccessUrl)
14464 amit.gupta 68
                soup = BeautifulSoup(rawHtml)
69
                try:
70
                    self.parseOldStlye(merchantOrder, soup)
71
                except:
14545 amit.gupta 72
                    try:
73
                        self.parseNewStlye(merchantOrder, soup)
74
                    except:
75
                        self.parseCancelled(merchantOrder, soup)
14319 amit.gupta 76
                resp['result'] = 'ORDER_CREATED'
77
                return resp    
13781 amit.gupta 78
            except:
13809 amit.gupta 79
                print "Error occurred"
80
                traceback.print_exc()
14312 amit.gupta 81
                resp['result'] = 'ORDER_NOT_CREATED'
13796 amit.gupta 82
                return resp    
13774 amit.gupta 83
 
13868 amit.gupta 84
    #This should be exposed from api for specific sources
85
    def scrapeStoreOrders(self):
86
        pass
87
 
14464 amit.gupta 88
    def parseOldStlye(self, merchantOrder, soup):
89
        merchantOrder.orderTrackingUrl = merchantOrder.orderSuccessUrl
90
        table = soup.body.findAll("table", recursive=False)[1]
91
        #print table
92
        tables = table.tr.td.findAll("table", recursive=False)
93
        for tr in tables[2].findAll("tr"):
94
            boldElement = tr.td.b
95
            if "Order Placed" in str(boldElement):
96
                merchantOrder.placedOn = boldElement.next_sibling.strip()
97
            if "order number" in str(boldElement):
98
                merchantOrder.merchantOrderId = boldElement.next_sibling.strip()
99
            if "Order Total" in str(boldElement):
100
                merchantOrder.paidAmount = int(float(boldElement.find('span').contents[-1].replace(',','')))
101
        anchors = table.tr.td.findAll("a", recursive=False)
102
        paymentAnchor = anchors.pop(-1)
103
 
104
        count = 0
105
        subOrders = []
106
        merchantOrder.subOrders = subOrders
107
        counter = 0 
108
        for anchor in anchors:
109
            count += 1
110
            tab = anchor.next_sibling
111
            status = MStore.ORDER_PLACED
112
            subStr = "Delivery #" + str(count) + ":"
113
            if subStr in  tab.find("b").text:
114
                detailedStatus = tab.find("b").text.replace(subStr, '').strip()
115
 
116
            tab = tab.next_sibling.next_sibling
117
            trs = tab.find("table").find('tbody').findAll("tr", recursive = False)
118
 
119
            estimatedDelivery = trs[0].td.find("b").next_sibling.strip()
120
 
121
            orderItemTrs = trs[1].findAll("td", recursive=False)[1].table.tbody.findAll("tr", recursive = False)
122
            i = -1
123
            for orderItemTr in orderItemTrs:
124
                i += 1
125
                if i%2 == 0:
126
                    continue
127
                counter += 1
128
                quantity =  int(re.findall(r'\d+', orderItemTr.td.contents[0])[0])
129
 
130
                productUrl = orderItemTr.td.contents[1].a["href"]
131
                productTitle = orderItemTr.td.contents[1].a.text
132
 
133
                unitPrice = int(float(orderItemTr.findAll('td')[1].span.text.replace('Rs. ','').replace(',','')))
134
 
135
 
136
                subOrder = SubOrder(productTitle, productUrl, merchantOrder.placedOn, unitPrice*quantity, status, quantity)
137
                subOrder.merchantSubOrderId = str(counter) + " of " + merchantOrder.merchantOrderId
138
                subOrder.estimatedDeliveryDate = estimatedDelivery
139
                subOrder.productCode = productUrl.split('/')[5]
140
                subOrder.detailedStatus = detailedStatus
141
                (cashbackAmount, percentage) = self.getCashbackAmount(subOrder.productCode, unitPrice*quantity)
142
                cashbackStatus = Store.CB_PENDING
143
                if cashbackAmount <= 0:
144
                    cashbackStatus = Store.CB_NA
145
                subOrder.cashBackStatus = cashbackStatus
146
                subOrder.cashBackAmount = cashbackAmount
147
                if percentage > 0:
148
                    subOrder.cashBackPercentage = percentage
149
                subOrders.append(subOrder)
150
        priceList = paymentAnchor.next_sibling.next_sibling.next_sibling.table.table.tbody.tbody.tbody.findAll('tr', recursive=False)
151
        totalAmount = 0
152
        grandAmount = 0
153
        for price in priceList:
154
            labelTd = price.td
155
            if 'Subtotal:' in labelTd.text:
156
                totalAmount += int(float(labelTd.next_sibling.next_sibling.find('span').contents[-1].replace(',','')))
157
            elif 'Grand Total:' in labelTd.text:
158
                grandAmount += int(float(labelTd.next_sibling.next_sibling.find('span').contents[-1].replace(',','')))
159
        if grandAmount < totalAmount:
160
            diff = totalAmount - grandAmount
161
            for subOrder in merchantOrder.subOrders:
162
                subOrder.amountPaid -= int(diff*(1-subOrder.amountPaid/totalAmount))
163
        self._updateToOrder(todict(merchantOrder))
164
 
165
    def parseNewStlye(self, merchantOrder, soup):
166
        merchantOrder.orderTrackingUrl = merchantOrder.orderSuccessUrl
167
        orderDetailsContainer = soup.body.find(id="orderDetails")
168
        orderLeftDiv = orderDetailsContainer.h1.next_sibling.next_sibling.div
169
        placedOnSpan = orderLeftDiv.find("span", {'class':'order-date-invoice-item'})
170
        merchantOrder.placedOn =placedOnSpan.text.split('Ordered on')[1].strip()
171
        merchantOrder.merchantOrderId = placedOnSpan.next_sibling.next_sibling.text.split('Order#')[1].strip()
172
        priceBox = orderDetailsContainer.find('div', {'class':re.compile(r'\ba-box-inner\b')}).div.div.findAll('div', recursive=False)[-1]
173
        priceRows = priceBox.findAll('div', {'class':'a-row'})
174
        subTotal = 0
175
        shippingPrice = 0
176
        promoApplied = 0
177
        for priceRow in priceRows:
178
            if "Item(s) Subtotal:" in str(priceRow):
179
                subTotal = int(float(priceRow.div.next_sibling.next_sibling.span.span.text.replace('Rs.','').replace(',', '')))
180
            elif "Shipping:" in str(priceRow):
181
                shippingPrice  = int(float(priceRow.div.next_sibling.next_sibling.span.span.text.replace('Rs.','').replace(',', '')))
182
            elif "Grand Total:" in str(priceRow):
183
                grandPrice  = int(float(priceRow.div.next_sibling.next_sibling.span.span.text.replace('Rs.','').replace(',', '')))
184
                merchantOrder.paidAmount = grandPrice
185
            elif "Total:" in str(priceRow):
186
                totalPrice  = int(float(priceRow.div.next_sibling.next_sibling.span.span.text.replace('Rs.','').replace(',', '')))
187
            elif "Promotion Applied:" in str(priceRow):
188
                promoApplied  += int(float(priceRow.div.next_sibling.next_sibling.span.span.text.replace('Rs.','').replace(',', '')))
189
        totalPaid = subTotal        
190
        if promoApplied > 0:
191
            totalPaid -= promoApplied
192
            if shippingPrice <= promoApplied:
193
                totalPaid  += shippingPrice
194
 
195
        shipmentDivs = orderDetailsContainer.find('div', {'class':'a-box shipment'}).findAll('div', recursive = False)
196
        subOrders = []
197
        merchantOrder.subOrders = subOrders
198
        i=1
199
        for shipmentDiv in shipmentDivs:
200
            innerBoxes = shipmentDiv.findAll('div', recursive = False)
201
            statusDiv = innerBoxes[0]
202
            subOrderStatus = statusDiv.div.span.text.strip()
203
            estimatedDeliveryDate = statusDiv.div.div.find_next_sibling('div').span.span.text.strip()
204
            productDivs = innerBoxes[-1].div.div.div.findAll('div', recursive=False)
205
            subOrders = []
206
            merchantOrder.subOrders = subOrders
207
            for i, productDiv in enumerate(productDivs):
208
                i +=1 
209
                imgDiv  = productDiv.div.div
210
                detailDiv = imgDiv.find_next_sibling('div')
211
                detailDivs = detailDiv.findAll('div', recursive=False)
212
                arr = detailDivs[0].a.text.strip().split(" of ", 1)
14465 amit.gupta 213
                (productTitle, quantity) = (arr[-1], (1 if len(arr)==1 else int(arr[0])) )
14464 amit.gupta 214
                unitPrice = int(float(detailDivs[2].span.text.replace('Rs. ','').replace(',','')))
215
                amountPaid = int((unitPrice*quantity*totalPaid)/subTotal)
216
                productUrl = "http://www.amazon.in" + detailDivs[0].a.get('href')
217
                subOrder = SubOrder(productTitle, productUrl, merchantOrder.placedOn, amountPaid, MStore.ORDER_PLACED, quantity)
218
                subOrder.productCode = productUrl.split('/')[5]
219
                subOrder.unitPrice = unitPrice
220
                subOrder.merchantSubOrderId = str(i) + " of " + merchantOrder.merchantOrderId 
221
                subOrder.estimatedDeliveryDate = estimatedDeliveryDate
222
                subOrder.detailedStatus = subOrderStatus
223
                subOrder.deliveryCharges = shippingPrice
14466 amit.gupta 224
                subOrder.imgUrl = imgDiv.img["src"]
14464 amit.gupta 225
                (cashbackAmount, percentage) = self.getCashbackAmount(subOrder.productCode, amountPaid)
226
                cashbackStatus = Store.CB_PENDING
227
                if cashbackAmount <= 0:
228
                    cashbackStatus = Store.CB_NA
229
                subOrder.cashBackStatus = cashbackStatus
230
                subOrder.cashBackAmount = cashbackAmount
231
                if percentage > 0:
232
                    subOrder.cashBackPercentage = percentage
233
                subOrders.append(subOrder)
234
        self._updateToOrder(todict(merchantOrder))
14545 amit.gupta 235
 
236
    def parseCancelled(self, merchantOrder,soup):
237
        fonts = soup.body.findAll("table", recursive=False)[1].findAll("font")
238
        if fonts[0].text == "Important Message":
239
            if fonts[1].text=="This order has been cancelled.":
240
                merchantOrder.closed = True
241
                merchantOrder.status = "Cancelled"
242
                merchantOrder.requireDetail = False
243
                self._updateToOrder(todict(merchantOrder))
244
            else:
245
                raise ParseException("parseCancelled", "Found detailed status" + fonts[1].text)
14464 amit.gupta 246
 
13868 amit.gupta 247
    def getTrackingUrls(self, userId):
14074 amit.gupta 248
 
249
        missingOrderUrls = []
250
        missingOrders = self._getMissingOrders({'userId':userId})
251
        for missingOrder in missingOrders:
252
            missingOrderUrls.append(ORDER_REDIRECT_URL%(missingOrder['merchantOrderId']))
13879 amit.gupta 253
        orders = self._getActiveOrders({'userId':userId})
13882 amit.gupta 254
        count = len(orders)
13879 amit.gupta 255
        print "count", count
13868 amit.gupta 256
        if count > 0:
14320 amit.gupta 257
            return missingOrderUrls + ['https://www.amazon.in/gp/css/order-history', 'https://www.amazon.in/gp/css/order-history/?orderFilter=cancelled', 'https://www.amazon.in/gp/css/order-history?orderFilter=cancelled&startIndex=10']
13959 amit.gupta 258
        else: 
14074 amit.gupta 259
            return missingOrderUrls
13927 amit.gupta 260
 
261
    def trackOrdersForUser(self, userId, url, rawHtml):
14285 amit.gupta 262
        directory = "/tmp/User" + str(userId)
263
        if not os.path.exists(directory):
264
            os.makedirs(directory)
14298 amit.gupta 265
        filename = directory + "/" + str(datetime.now())
266
        print "filename---", filename
267
        f = open(filename,'w')
14173 amit.gupta 268
        f.write(rawHtml) # python will convert \n to os.linesep
269
        f.close() # you can omit in most cases as the destructor will call if
270
 
13995 amit.gupta 271
        try:
272
            searchMap = {'userId':userId}
273
            collectionMap = {'merchantOrderId':1}
274
            activeOrders = self._getActiveOrders(searchMap, collectionMap)
14033 amit.gupta 275
            datetimeNow = datetime.now()
276
            timestamp = int(time.mktime(datetimeNow.timetuple()))
14009 amit.gupta 277
            print "url----------------", url
14008 amit.gupta 278
 
14320 amit.gupta 279
            if url == 'https://www.amazon.in/gp/css/order-history' or 'https://www.amazon.in/gp/css/order-history/?orderFilter=cancelled' in url:
13995 amit.gupta 280
                soup = BeautifulSoup(rawHtml)
281
                allOrders = soup.find(id="ordersContainer").findAll('div', {'class':'a-box-group a-spacing-base order'})
282
                bulk = self.db.merchantOrder.initialize_ordered_bulk_op()
283
                for activeOrder in activeOrders:
284
                    for orderEle in allOrders:
285
                        orderdiv = orderEle.find('div', {'class':'a-box a-color-offset-background order-info'}).find('div', {'class':'a-fixed-right-grid-col actions a-col-right'})
286
                        merchantOrderId = orderdiv.find('span', {'class':'a-color-secondary value'}).text.strip()
287
                        if merchantOrderId==activeOrder['merchantOrderId']:
288
                            closed = True
289
                            shipments = orderEle.findAll('div',{'class':re.compile('.*?a-box.*?')}, recursive=False)
290
                            shipments.pop(0)
291
                            for shipment in shipments:
292
                                shipdiv = shipment.find('div', {'class':'a-box-inner'})
14050 amit.gupta 293
                                sdivs = shipment.div.div.findAll('div', recursive=False)
14065 amit.gupta 294
                                orderStatus = sdivs[0].span.text.strip()
14050 amit.gupta 295
                                status = self._getStatusFromDetailedStatus(orderStatus)
296
                                if status == MStore.ORDER_DELIVERED:
14299 amit.gupta 297
                                    deliveredOn = sdivs[0].findAll('span')[-1].text.strip()
14050 amit.gupta 298
                                    deliveredOn = deliveredOn.split(":")[1].strip()
14335 amit.gupta 299
                                deliveryestimatespan = sdivs[0].find('span', {'class':'a-color-success'})
300
                                deliveryEstimate = None
301
                                if deliveryestimatespan is not None:
302
                                    deliveryEstimate = deliveryestimatespan.find('span', {'class':'a-text-bold'}).text.strip()
303
                                productDivs = shipdiv.find('div', {'class':re.compile('.*?a-spacing-top-medium.*?')}).find('div', {'class':'a-row'}).findAll('div', recursive=False)
13995 amit.gupta 304
                                trackingUrl = None
305
                                for buttonDiv in shipdiv.findAll('span', {'class':'a-button-inner'}):
306
                                    if buttonDiv.find('a').text.strip()=='Track package':
307
                                        trackingUrl = buttonDiv.find('a')['href'].strip()
308
                                        if not trackingUrl.startswith("http"):
309
                                            trackingUrl = "https://www.amazon.in/" + trackingUrl
310
                                        break
311
                                for prodDiv in productDivs:
312
                                    prodDiv.find('div', {'class':'a-fixed-left-grid-inner'})
313
                                    productTitle = prodDiv.find('div', {'class':'a-fixed-left-grid-inner'}).find("div", {'class':'a-row'}).find('a').text.strip()
314
                                    imgUrl = prodDiv.find("img")["src"]
315
                                    for subOrder in activeOrder['subOrders']:
316
                                        if subOrder['productTitle'] == productTitle:
317
                                            findMap = {"orderId": activeOrder['orderId'], "subOrders.merchantSubOrderId": subOrder.get("merchantSubOrderId")}
318
                                            updateMap = {}
319
                                            closedStatus = False
320
                                            updateMap['subOrders.$.imgUrl'] = imgUrl
14033 amit.gupta 321
                                            updateMap['subOrders.$.lastTracked'] = timestamp
13995 amit.gupta 322
                                            updateMap['subOrders.$.detailedStatus'] = orderStatus
323
                                            cashbackStatus = subOrder.get("cashBackStatus")
324
                                            updateMap['subOrders.$.status'] = status 
325
 
326
                                            if status==MStore.ORDER_DELIVERED:                               
14291 amit.gupta 327
                                                updateMap['subOrders.$.deliveredOn'] = deliveredOn
13995 amit.gupta 328
                                                closedStatus = True
329
                                                updateMap['subOrders.$.closed'] = True
330
                                                if cashbackStatus == Store.CB_PENDING:
331
                                                    updateMap['subOrders.$.cashbackStatus'] = Store.CB_APPROVED
332
                                            if status==MStore.ORDER_CANCELLED:     
333
                                                closedStatus = True
334
                                                updateMap['subOrders.$.closed'] = True
335
                                                if cashbackStatus == Store.CB_PENDING:
336
                                                    updateMap['subOrders.$.cashBackStatus'] = Store.CB_CANCELLED
337
                                            if status==MStore.ORDER_SHIPPED:   
338
                                                updateMap['subOrders.$.estimatedDeliveryDate'] = deliveryEstimate
339
                                                if trackingUrl is not None:
340
                                                    updateMap['subOrders.$.trackingUrl'] = trackingUrl
341
                                            if not closedStatus:
342
                                                closed = False
343
                                            bulk.find(findMap).update({'$set' : updateMap})
344
                                            break
345
                            bulk.find({'orderId': activeOrder['orderId']}).update({'$set':{'closed': closed}})            
14080 amit.gupta 346
                bulk.execute()
347
                return 'PARSED_SUCCESS'
348
            else:
14086 amit.gupta 349
                merchantOrderId = re.findall(r'https://www.amazon.in/gp/css/summary/edit.html\?orderID=(.*)?', url, re.IGNORECASE)[0] 
14085 amit.gupta 350
                merchantOrder = self.db.merchantOrder.find_one({"merchantOrderId":merchantOrderId})
14312 amit.gupta 351
                self.parseOrderRawHtml(merchantOrder['orderId'], merchantOrder['subTagId'], merchantOrder['userId'], rawHtml, url, False)['result']
14080 amit.gupta 352
                return 'PARSED_SUCCESS'
353
                pass
14008 amit.gupta 354
            return 'PARSED_SUCCESS_NO_ORDERS'
13995 amit.gupta 355
        except:
356
            traceback.print_exc()    
357
            return 'PARSED_FAILED'
358
 
359
    def _getStatusFromDetailedStatus(self, detailedStatus):
14061 amit.gupta 360
        if "ordered from" in detailedStatus.lower():
361
            return MStore.ORDER_PLACED 
362
 
13995 amit.gupta 363
        for key, value in self.orderStatusRegexMap.iteritems():
14032 amit.gupta 364
            if detailedStatus.lower() in value:
13995 amit.gupta 365
                return key
366
 
367
        print "Detailed Status need to be mapped"
14062 amit.gupta 368
        print "Found new order status", detailedStatus
13995 amit.gupta 369
        raise ParseException("_getStatusFromDetailedStatus", "Found new order status" + detailedStatus)
13927 amit.gupta 370
 
371
 
13774 amit.gupta 372
def main():
373
    store = getStore(1)
14464 amit.gupta 374
    #store.scrapeStoreOrders()    
14545 amit.gupta 375
    store.parseOrderRawHtml(1, 'saad', '21232211', readSSh('/home/amit/Downloads/710.html'), 'https://www.amazon.in/gp/css/summary/edit.html?orderID=12212')
14464 amit.gupta 376
    #store.trackOrdersForUser(8,'https://www.amazon.in/gp/css/order-history', readSSh('/tmp/User2/2015-03-03 00:29:40.165513'))
377
    #readSSh('snapdeal.csv')
14162 amit.gupta 378
def readSSh(fileName):
379
    try:
380
            str1 = open(fileName).read()
381
            return str1
382
    except:
383
        ssh_client = SSHClient()
384
        str1 = ""
385
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
386
        ssh_client.connect('104.200.25.40', 22, 'root', 'ecip$dtrMay2014')
387
        sftp_client = ssh_client.open_sftp()
388
        try:
14299 amit.gupta 389
            if not os.path.exists(os.path.dirname(fileName)):
390
                os.makedirs(os.path.dirname(fileName))
14162 amit.gupta 391
            sftp_client.get(fileName, fileName)
392
            try:
393
                str1 = open(fileName).read()
394
                return str1
395
            finally:
396
                pass
397
        except:
398
            "could not read"
399
        return str1
13774 amit.gupta 400
 
401
 
402
if __name__ == '__main__':
14284 amit.gupta 403
        main()