gopichandra commited on
Commit
0504d2d
Β·
verified Β·
1 Parent(s): c79235e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -349
app.py CHANGED
@@ -1,4 +1,4 @@
1
- import os
2
  from paddleocr import PaddleOCR
3
  from PIL import Image
4
  import gradio as gr
@@ -10,7 +10,7 @@ import matplotlib.pyplot as plt
10
  from io import BytesIO
11
  from fuzzywuzzy import process
12
 
13
- #πŸ“Œ Attribute mappings: readable names to Salesforce API names
14
  ATTRIBUTE_MAPPING = {
15
  "Product name": "Productname__c",
16
  "Colour": "Colour__c",
@@ -103,12 +103,12 @@ PRODUCT_NAMES = [
103
  "mono block pumps"
104
  ]
105
 
106
- #πŸ“Œ Salesforce credentials
107
  SALESFORCE_USERNAME = "venkatramana@sandbox.com"
108
  SALESFORCE_PASSWORD = "Venkat12345@"
109
  SALESFORCE_SECURITY_TOKEN = "GhcJJmjBEefdnukJoz4CAQlR"
110
 
111
- #🧠 Initialize PaddleOCR
112
  ocr = PaddleOCR(use_angle_cls=True, lang='en')
113
 
114
  # Function to extract text using PaddleOCR
@@ -148,7 +148,7 @@ def extract_attributes(extracted_text):
148
  def filter_valid_attributes(attributes, valid_fields):
149
  return {ATTRIBUTE_MAPPING[key]: value for key, value in attributes.items() if ATTRIBUTE_MAPPING[key] in valid_fields}
150
 
151
- #πŸ“Š Function to interact with Salesforce based on mode and type
152
  def interact_with_salesforce(mode, entry_type, quantity, extracted_text):
153
  try:
154
  sf = Salesforce(
@@ -205,7 +205,8 @@ def interact_with_salesforce(mode, entry_type, quantity, extracted_text):
205
 
206
  if response["records"]:
207
  record_id = response["records"][0]["Id"]
208
- updated_quantity = quantity # Overwrite the quantity, don't add
 
209
  sf_object.update(record_id, {field_name: updated_quantity})
210
  return f"Updated record for product '{product_name}' in {object_name}. New {field_name}: {updated_quantity}."
211
  else:
@@ -214,101 +215,60 @@ def interact_with_salesforce(mode, entry_type, quantity, extracted_text):
214
  filtered_attributes = filter_valid_attributes(attributes, valid_fields)
215
  filtered_attributes[field_name] = quantity
216
  sf_object.create(filtered_attributes)
217
- return f"βœ… Data successfully exported to Salesforce object {object_name}."
218
 
219
  except Exception as e:
220
- return f"❌ Error interacting with Salesforce: {str(e)}"
221
- import pandas as pd
222
- import matplotlib.pyplot as plt
223
- from io import BytesIO
224
- from PIL import Image
225
- from simple_salesforce import Salesforce
226
- import gradio as gr
227
 
228
- # πŸ“Œ Salesforce Credentials
229
- SALESFORCE_USERNAME = "venkatramana@sandbox.com"
230
- SALESFORCE_PASSWORD = "Venkat12345@"
231
- SALESFORCE_SECURITY_TOKEN = "GhcJJmjBEefdnukJoz4CAQlR"
232
-
233
- # πŸ“Œ Function to Fetch and Display Salesforce Data (Stock Table & Graph)
234
- def fetch_salesforce_data():
235
  try:
236
  sf = Salesforce(
237
  username=SALESFORCE_USERNAME,
238
  password=SALESFORCE_PASSWORD,
239
  security_token=SALESFORCE_SECURITY_TOKEN
240
  )
 
 
 
 
241
 
242
- # SOQL Query to fetch stock details
243
- query = "SELECT Product_Name__c, Current_Stocks__c FROM Inventory_Management__c LIMIT 100"
244
- response = sf.query(query)
245
- records = response.get('records', [])
246
-
247
- if not records:
248
- return "<p>No Data Found</p>", None # Return empty result if no records found
249
-
250
- # Convert Salesforce Data into Pandas DataFrame
251
- df = pd.DataFrame(records)
252
- df.rename(columns={'Product_Name__c': 'Product Name', 'Current_Stocks__c': 'Current Stocks'}, inplace=True)
253
- df.drop(columns=['attributes'], inplace=True, errors='ignore')
254
-
255
- # Convert DataFrame to HTML Table
256
- table_html = df.to_html(index=False)
257
-
258
- # Generate Inventory Graph
259
- graph_image = generate_inventory_graph(df)
260
-
261
- return table_html, graph_image
262
-
263
  except Exception as e:
264
- return f"❌ Error fetching data: {str(e)}", None
265
 
266
- # πŸ“Š Function to Generate Inventory Stock Graph
267
- def generate_inventory_graph(df):
268
  try:
269
- fig, ax = plt.subplots(figsize=(12, 6))
270
- df.plot(kind='bar', x="Product Name", y="Current Stocks", ax=ax, legend=False, color="royalblue")
271
- ax.set_title("Inventory Stock Distribution", fontsize=14, fontweight="bold")
272
- ax.set_xlabel("Product Name", fontsize=12)
273
- ax.set_ylabel("Stock Quantity", fontsize=12)
274
  plt.xticks(rotation=45, ha="right", fontsize=10)
275
  plt.tight_layout()
276
-
277
- # Save the graph to a buffer
278
  buffer = BytesIO()
279
  plt.savefig(buffer, format="png")
280
  buffer.seek(0)
281
-
282
- return Image.open(buffer)
283
-
284
  except Exception as e:
285
- return None # Return None if graph generation fails
286
-
287
- # πŸ“Œ Gradio UI for Viewing Salesforce Data (Table & Graph)
288
- def app():
289
- with gr.Blocks() as interface:
290
- gr.Markdown("<h1>πŸ“Š Salesforce Stock & Inventory Overview</h1>")
291
-
292
- with gr.Row():
293
- with gr.Column():
294
- gr.Markdown("<h3>πŸ“‹ Stock Data Table</h3>")
295
- salesforce_table = gr.HTML(label="πŸ“¦ Inventory Table")
296
-
297
- with gr.Column():
298
- gr.Markdown("<h3>πŸ“ˆ Inventory Stock Graph</h3>")
299
- salesforce_graph = gr.Image(type="pil", label="πŸ“‰ Stock Graph")
300
-
301
- generate_button = gr.Button("⚑ Fetch Data")
302
-
303
- # Clicking "Fetch Data" will display the table & graph
304
- generate_button.click(fn=fetch_salesforce_data, inputs=[], outputs=[salesforce_table, salesforce_graph])
305
-
306
- return interface
307
-
308
- # πŸš€ Launch the Gradio App
309
- if __name__ == "__main__":
310
- interface = app()
311
- interface.launch(share=True)
312
 
313
  # Unified function to handle image processing and Salesforce interaction
314
  def process_image(image, mode, entry_type, quantity):
@@ -327,276 +287,45 @@ def process_image(image, mode, entry_type, quantity):
327
  numbered_output = "\n".join([f"{key}: {value}" for key, value in attributes.items()])
328
  return f"Extracted Text:\n{extracted_text}\n\nAttributes and Values:\n{numbered_output}", message
329
 
330
-
331
- import gradio as gr
332
- import numpy as np
333
- from PIL import Image
334
-
335
- # Function to fetch and return Salesforce data
336
- def generate_salesforce_data():
337
  df = format_salesforce_data()
 
 
 
338
  if df is not None:
339
  table_component = df.to_html(index=False)
340
  bar_graph_image = generate_bar_graph(df)
341
- return table_component, bar_graph_image
342
- return "<p>No Data Available</p>", None
343
-
344
- def app():
345
- with gr.Blocks(css="""
346
- /* General Styling */
347
- .gradio-container {
348
- background: #0b0f29; /* Deep Dark Blue */
349
- font-family: 'Poppins', sans-serif;
350
- color: white;
351
- padding: 10px;
352
- }
353
-
354
- h1, h2, h3 {
355
- text-align: center;
356
- color: white;
357
- font-weight: bold;
358
- text-shadow: 0px 0px 10px rgba(255,255,255,0.8);
359
- }
360
-
361
- /* Cards & Elements */
362
- .card {
363
- background: rgba(255, 255, 255, 0.1);
364
- border-radius: 15px;
365
- padding: 20px;
366
- width: 100%;
367
- max-width: 900px; /* Prevents extra-wide tables */
368
- margin: auto; /* Centers the table */
369
- box-shadow: 0px 0px 25px rgba(0, 255, 255, 0.6);
370
- transition: 0.3s;
371
- }
372
-
373
- .card:hover {
374
- box-shadow: 0px 0px 30px rgba(0, 255, 255, 0.9);
375
- }
376
-
377
- /* Buttons */
378
- .gradio-button {
379
- background: linear-gradient(to right, #ff416c, #ff4b2b);
380
- color: white !important;
381
- border-radius: 10px !important;
382
- font-weight: bold;
383
- padding: 12px;
384
- transition: all 0.3s ease-in-out;
385
- box-shadow: 0px 0px 20px rgba(255, 0, 255, 0.6);
386
- }
387
-
388
- .gradio-button:hover {
389
- background: linear-gradient(to right, #ff4b2b, #ff416c);
390
- box-shadow: 0px 0px 30px rgba(255, 0, 255, 0.9);
391
- }
392
-
393
- /* Tabs */
394
- .gradio-tab {
395
- font-size: 16px;
396
- color: white;
397
- font-weight: bold;
398
- text-shadow: 0px 0px 10px rgba(255, 255, 255, 0.6);
399
- }
400
-
401
- /* Scrollable Table */
402
- .table-container {
403
- width: 100%;
404
- max-width: 900px;
405
- max-height: 400px; /* Controls table height */
406
- overflow-y: auto; /* Enables vertical scrolling */
407
- overflow-x: auto; /* Enables horizontal scrolling if needed */
408
- display: flex;
409
- justify-content: center;
410
- margin: auto;
411
- }
412
-
413
- table {
414
- width: 100%;
415
- border-collapse: collapse;
416
- background: rgba(255, 255, 255, 0.1);
417
- color: white;
418
- min-width: 600px;
419
- }
420
-
421
- th, td {
422
- padding: 12px;
423
- text-align: left;
424
- border-bottom: 1px solid rgba(255, 255, 255, 0.2);
425
- font-size: 14px;
426
- }
427
-
428
- th {
429
- background: rgba(0, 255, 255, 0.2);
430
- }
431
-
432
- /* Mobile View */
433
- @media (max-width: 768px) {
434
- .gradio-container {
435
- padding: 10px;
436
- }
437
-
438
- .card {
439
- width: 100%;
440
- max-width: 100%;
441
- padding: 10px;
442
- }
443
-
444
- .table-container {
445
- max-height: 350px; /* Adjusts for mobile */
446
- overflow-y: scroll; /* Ensures vertical scrolling */
447
- }
448
-
449
- table {
450
- width: 100%;
451
- min-width: 100%;
452
- }
453
-
454
- th, td {
455
- font-size: 12px;
456
- padding: 8px;
457
- }
458
- }
459
-
460
- """) as interface:
461
-
462
- gr.Markdown("<h1>🏒 VENKATARAMANA MOTORS</h1>")
463
-
464
- with gr.Tab("πŸ“₯ Stock Entry & Processing"):
465
- with gr.Row():
466
- with gr.Column():
467
- gr.Markdown("<h3>πŸ“Œ Upload & Process</h3>")
468
- image_input = gr.Image(type="numpy", label="πŸ“„ Upload Image", elem_classes="card")
469
- mode_dropdown = gr.Dropdown(label="πŸ“Œ Mode", choices=["Entry", "Exit"], value="Entry", elem_classes="card")
470
- entry_type_radio = gr.Radio(label="πŸ“¦ Entry Type", choices=["Sales", "Non-Sales"], value="Sales", elem_classes="card")
471
- quantity_input = gr.Number(label="πŸ”’ Quantity", value=1, interactive=True, elem_classes="card")
472
-
473
- with gr.Column():
474
- gr.Markdown("<h3>πŸ“Š Processed Data</h3>")
475
- image_view = gr.Text(label="πŸ“œ Image Data Viewer", interactive=False, elem_classes="card")
476
- result_output = gr.Text(label="πŸ“ Processed Result", interactive=False, elem_classes="card")
477
- submit_button = gr.Button("πŸ” Process Image", elem_id="process-btn", elem_classes="gradio-button")
478
-
479
- with gr.Tab("πŸ“Š Salesforce Data Overview"):
480
- gr.Markdown("<h3>πŸ“‘ Stock Table</h3>")
481
- with gr.Row(elem_classes="table-container"):
482
- salesforce_table = gr.HTML(label="πŸ“¦ Salesforce Data Table", elem_classes="card")
483
-
484
- gr.Markdown("<h3>πŸ“ˆ Inventory Analytics</h3>")
485
- with gr.Row():
486
- salesforce_graph = gr.Image(type="pil", label="πŸ“‰ Stock Distribution Bar Graph", elem_classes="card")
487
-
488
- generate_button = gr.Button("⚑ Generate Data", elem_classes="gradio-button")
489
-
490
- # Clicking "Generate Data" fetches table & graph
491
- generate_button.click(fn=generate_salesforce_data, inputs=[], outputs=[salesforce_table, salesforce_graph])
492
-
493
- submit_button.click(fn=process_image, inputs=[image_input, mode_dropdown, entry_type_radio, quantity_input], outputs=[image_view, result_output])
494
-
495
- return interface
496
-
497
- interface = app()
498
- interface.launch()
499
- css = """
500
- body {
501
- background: linear-gradient(135deg, #282c34, #4b79a1);
502
- font-family: 'Poppins', sans-serif;
503
- }
504
-
505
- .gradio-container {
506
- border-radius: 20px;
507
- padding: 30px;
508
- background: rgba(255, 255, 255, 0.2);
509
- backdrop-filter: blur(10px);
510
- box-shadow: 0px 8px 30px rgba(0, 123, 255, 0.4);
511
- border: 2px solid rgba(255, 255, 255, 0.4);
512
- }
513
-
514
- .gradio-title {
515
- font-size: 48px;
516
- font-weight: bold;
517
- text-align: center;
518
- background: linear-gradient(90deg, #ff7eb3, #ff758c);
519
- -webkit-background-clip: text;
520
- -webkit-text-fill-color: transparent;
521
- text-shadow: 5px 5px 20px rgba(255, 105, 180, 0.6);
522
- margin-bottom: 20px;
523
- animation: glow 2s infinite alternate;
524
- }
525
-
526
- @keyframes glow {
527
- from {
528
- text-shadow: 5px 5px 30px rgba(255, 87, 134, 0.6);
529
- }
530
- to {
531
- text-shadow: 6px 6px 40px rgba(255, 54, 90, 1);
532
- }
533
- }
534
-
535
- .gradio-box {
536
- border-radius: 15px;
537
- padding: 25px;
538
- background: linear-gradient(135deg, #6a11cb, #2575fc);
539
- box-shadow: 0px 6px 25px rgba(30, 144, 255, 0.6);
540
- border: 2px solid #6a5acd;
541
- color: white;
542
- font-size: 18px;
543
- }
544
-
545
- .gradio-button {
546
- border-radius: 12px;
547
- padding: 18px 36px;
548
- font-size: 20px;
549
- font-weight: bold;
550
- color: #fff;
551
- background: linear-gradient(135deg, #1e90ff, #00bfff);
552
- box-shadow: 0px 6px 25px rgba(0, 191, 255, 0.6);
553
- transition: all 0.3s ease-in-out;
554
- }
555
-
556
- .gradio-button:hover {
557
- background: linear-gradient(135deg, #00bfff, #1e90ff);
558
- box-shadow: 0px 10px 35px rgba(0, 191, 255, 0.9);
559
- transform: scale(1.1);
560
- }
561
-
562
- .gradio-input {
563
- border-radius: 10px;
564
- padding: 16px;
565
- font-size: 18px;
566
- background: rgba(255, 255, 255, 0.3);
567
- border: 2px solid rgba(0, 123, 255, 0.5);
568
- color: #fff;
569
- transition: 0.3s;
570
- }
571
-
572
- .gradio-input:focus {
573
- border: 2px solid #1e90ff;
574
- outline: none;
575
- box-shadow: 0px 5px 20px rgba(30, 144, 255, 0.6);
576
- }
577
-
578
- .gradio-output {
579
- background: linear-gradient(135deg, #f39c12, #e74c3c);
580
- padding: 22px;
581
- border-radius: 15px;
582
- color: white;
583
- font-size: 20px;
584
- text-align: center;
585
- border: 2px solid #f39c12;
586
- }
587
-
588
- .gradio-file {
589
- background: linear-gradient(135deg, #8e44ad, #3498db);
590
- color: white;
591
- padding: 16px;
592
- border-radius: 15px;
593
- text-align: center;
594
- font-size: 18px;
595
- }
596
- """
597
-
598
-
599
-
600
 
601
  if __name__ == "__main__":
602
- app().launch(share=True)
 
1
+ mport os
2
  from paddleocr import PaddleOCR
3
  from PIL import Image
4
  import gradio as gr
 
10
  from io import BytesIO
11
  from fuzzywuzzy import process
12
 
13
+ # Attribute mappings: readable names to Salesforce API names
14
  ATTRIBUTE_MAPPING = {
15
  "Product name": "Productname__c",
16
  "Colour": "Colour__c",
 
103
  "mono block pumps"
104
  ]
105
 
106
+ # Salesforce credentials
107
  SALESFORCE_USERNAME = "venkatramana@sandbox.com"
108
  SALESFORCE_PASSWORD = "Venkat12345@"
109
  SALESFORCE_SECURITY_TOKEN = "GhcJJmjBEefdnukJoz4CAQlR"
110
 
111
+ # Initialize PaddleOCR
112
  ocr = PaddleOCR(use_angle_cls=True, lang='en')
113
 
114
  # Function to extract text using PaddleOCR
 
148
  def filter_valid_attributes(attributes, valid_fields):
149
  return {ATTRIBUTE_MAPPING[key]: value for key, value in attributes.items() if ATTRIBUTE_MAPPING[key] in valid_fields}
150
 
151
+ # Function to interact with Salesforce based on mode and type
152
  def interact_with_salesforce(mode, entry_type, quantity, extracted_text):
153
  try:
154
  sf = Salesforce(
 
205
 
206
  if response["records"]:
207
  record_id = response["records"][0]["Id"]
208
+ current_quantity = response["records"][0].get(field_name, 0) or 0
209
+ updated_quantity = max(0, current_quantity + quantity)
210
  sf_object.update(record_id, {field_name: updated_quantity})
211
  return f"Updated record for product '{product_name}' in {object_name}. New {field_name}: {updated_quantity}."
212
  else:
 
215
  filtered_attributes = filter_valid_attributes(attributes, valid_fields)
216
  filtered_attributes[field_name] = quantity
217
  sf_object.create(filtered_attributes)
218
+ return f"Data successfully exported to Salesforce object {object_name}."
219
 
220
  except Exception as e:
221
+ return f"Error interacting with Salesforce: {str(e)}"
 
 
 
 
 
 
222
 
223
+ # Function to pull data from Salesforce MotorDataAPI
224
+ def pull_data_from_motor_api():
 
 
 
 
 
225
  try:
226
  sf = Salesforce(
227
  username=SALESFORCE_USERNAME,
228
  password=SALESFORCE_PASSWORD,
229
  security_token=SALESFORCE_SECURITY_TOKEN
230
  )
231
+ motor_data = sf.apexecute("MotorDataAPI/", method="GET")
232
+ return motor_data # API returns the list of records
233
+ except Exception as e:
234
+ return f"Error pulling data from MotorDataAPI: {str(e)}"
235
 
236
+ # Function to format Salesforce data into a DataFrame with readable headers
237
+ def format_salesforce_data():
238
+ try:
239
+ data = pull_data_from_motor_api()
240
+ if isinstance(data, list):
241
+ df = pd.DataFrame(data)
242
+ df = df.rename(columns={
243
+ "Product_Name__c": "Product Name",
244
+ "Modal_Name__c": "Model Name",
245
+ "Current_Stocks__c": "Current Stocks"
246
+ })
247
+ # Remove unnecessary columns
248
+ df = df[["Product Name", "Model Name", "Current Stocks"]]
249
+ return df
250
+ else:
251
+ return None
 
 
 
 
 
252
  except Exception as e:
253
+ return None
254
 
255
+ # Function to generate a bar graph from Salesforce data
256
+ def generate_bar_graph(df):
257
  try:
258
+ fig, ax = plt.subplots(figsize=(12, 8))
259
+ df.plot(kind='bar', x="Product Name", y="Current Stocks", ax=ax, legend=False)
260
+ ax.set_title("Stock Distribution by Product Name")
261
+ ax.set_xlabel("Product Name")
262
+ ax.set_ylabel("Current Stocks")
263
  plt.xticks(rotation=45, ha="right", fontsize=10)
264
  plt.tight_layout()
 
 
265
  buffer = BytesIO()
266
  plt.savefig(buffer, format="png")
267
  buffer.seek(0)
268
+ img = Image.open(buffer)
269
+ return img
 
270
  except Exception as e:
271
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
 
273
  # Unified function to handle image processing and Salesforce interaction
274
  def process_image(image, mode, entry_type, quantity):
 
287
  numbered_output = "\n".join([f"{key}: {value}" for key, value in attributes.items()])
288
  return f"Extracted Text:\n{extracted_text}\n\nAttributes and Values:\n{numbered_output}", message
289
 
290
+ # Gradio Interface
291
+ def app():
 
 
 
 
 
292
  df = format_salesforce_data()
293
+ table_component = None
294
+ bar_graph_component = None
295
+
296
  if df is not None:
297
  table_component = df.to_html(index=False)
298
  bar_graph_image = generate_bar_graph(df)
299
+ if bar_graph_image:
300
+ bar_graph_component = bar_graph_image
301
+
302
+ return gr.TabbedInterface([
303
+ gr.Interface(
304
+ fn=process_image,
305
+ inputs=[
306
+ gr.Image(type="numpy", label="πŸ“„α΄œα΄˜ΚŸα΄α΄€α΄… Ιͺᴍᴀɒᴇ"),
307
+ gr.Dropdown(label="πŸ“ŒMode", choices=["ᴇɴᴛʀʏ", "ᴇxΙͺα΄›"], value="Entry"),
308
+ gr.Radio(label="Entry Type", choices=["κœ±α΄€ΚŸα΄‡κœ±", "ɴᴏɴ-κœ±α΄€ΚŸα΄‡κœ±"], value="Sales"),
309
+ gr.Number(label="πŸ”’Quantity", value=1, interactive=True),
310
+ ],
311
+ outputs=[
312
+ gr.Text(label="Ιͺᴍᴀɒᴇ α΄…α΄€α΄›α΄€ α΄ Ιͺᴇᴑᴇʀ"),
313
+ gr.Text(label="πŸ“οΌ²οΌ₯οΌ³οΌ΅οΌ¬οΌ΄")
314
+ ],
315
+ title="πŸ’π‘½π‘¬π‘΅π‘²π‘¨π‘»π‘¨π‘Ήπ‘¨π‘΄π‘¨π‘΅π‘¨ 𝑴𝑢𝑻𝑢𝑹𝑺",
316
+ description="πŸ“¦πˆππ•π„ππ“πŽπ‘π˜ πŒπ€ππ€π†π„πŒπ„ππ“"
317
+ ),
318
+ gr.Interface(
319
+ fn=lambda: (table_component, bar_graph_component),
320
+ inputs=[],
321
+ outputs=[
322
+ gr.HTML(label="πŸ“ˆκœ±α΄€ΚŸα΄‡κœ±κœ°α΄Κ€α΄„α΄‡ α΄…α΄€α΄›α΄€ ᴇxα΄˜α΄Κ€α΄›"),
323
+ gr.Image(type="pil", label="Stock Distribution Bar Graph")
324
+ ],
325
+ title="Salesforce Data",
326
+ description="View structured Salesforce data as a table and bar graph."
327
+ )
328
+ ], ["Processing", "πŸ“ŠSalesforce Data Export"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
  if __name__ == "__main__":
331
+ app().launch(share=True)